feat: Raycast annotation now labels every UI-split closed region separately - #1667
Conversation
Previously a single PhysicsCollider GameObject produced one AnnotatedElements entry even when UI occlusion carved its reachable samples into multiple visually closed regions on screen. Agents could only address one of those regions with SimX/SimY, and the screenshot overlay drew a single label even when several disjoint outlines were visible. Split the reachable cluster into 4-connected components (matching the outline builder's neighbor definition) and emit one AnnotatedElements entry per component. Path, Name, Layer, and Components stay shared across sibling entries; Label, Bounds, SimX/SimY, and RaycastOutlineSegments are independent so each closed area is separately clickable. Label numbers remain a single R1/R2/R3... sequence across all entries. Component ordering uses min InputY, then min InputX, with a min (Row, Column) lexicographic tiebreaker so the label sequence is fully deterministic across runs even when the first two keys tie.
Explain that a single PhysicsCollider GameObject can now appear as multiple AnnotatedElements entries when UI occlusion splits its reachable samples into disjoint 4-connected regions, and describe how simulate-mouse-ui users should reach a specific region (SimX/SimY) versus letting Unity pick one (--target-path).
📝 WalkthroughWalkthroughThis PR adds connected-component splitting of raycast samples in RaycastHitClusterer, and updates RaycastGridAnnotator to generate a separate UIElementInfo per 4-connected region within a PhysicsCollider cluster, with deterministic top-left-first label ordering. Corresponding unit tests and documentation updates describe the new multi-entry behavior. ChangesPhysicsCollider region-splitting
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant RaycastGridAnnotator
participant RaycastHitClusterer
participant CreateComponentElements
RaycastGridAnnotator->>RaycastHitClusterer: SplitIntoConnectedComponents(reachableSamples)
RaycastHitClusterer-->>RaycastGridAnnotator: connected components
RaycastGridAnnotator->>CreateComponentElements: components, startLabelNumber
CreateComponentElements->>CreateComponentElements: sort via CompareComponentsByTopLeft
CreateComponentElements-->>RaycastGridAnnotator: UIElementInfo per component
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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)
Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.cs (1)
192-216: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: collapse the duplicated
MinInputX/MinInputYscanners.The two helpers are identical except for the accessed field. A single helper taking a selector removes the duplication without changing behavior.
♻️ Optional consolidation
- private static float MinInputX(List<RaycastClusterSample> samples) - { - float minValue = samples[0].InputX; - for (int i = 1; i < samples.Count; i++) - { - if (samples[i].InputX < minValue) - { - minValue = samples[i].InputX; - } - } - return minValue; - } - - private static float MinInputY(List<RaycastClusterSample> samples) - { - float minValue = samples[0].InputY; - for (int i = 1; i < samples.Count; i++) - { - if (samples[i].InputY < minValue) - { - minValue = samples[i].InputY; - } - } - return minValue; - } + private static float MinInputX(List<RaycastClusterSample> samples) => + MinInput(samples, static sample => sample.InputX); + + private static float MinInputY(List<RaycastClusterSample> samples) => + MinInput(samples, static sample => sample.InputY); + + private static float MinInput( + List<RaycastClusterSample> samples, + System.Func<RaycastClusterSample, float> selector) + { + float minValue = selector(samples[0]); + for (int i = 1; i < samples.Count; i++) + { + float value = selector(samples[i]); + if (value < minValue) + { + minValue = value; + } + } + return minValue; + }🤖 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 `@Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.cs` around lines 192 - 216, MinInputX and MinInputY in RaycastGridAnnotator are duplicate scanners that only differ by the field they read. Refactor them into one shared helper in the same class that accepts a selector for the input coordinate, then update the existing callers to use that helper while keeping behavior unchanged.
🤖 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
`@Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.cs`:
- Around line 192-216: MinInputX and MinInputY in RaycastGridAnnotator are
duplicate scanners that only differ by the field they read. Refactor them into
one shared helper in the same class that accepts a selector for the input
coordinate, then update the existing callers to use that helper while keeping
behavior unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2428b2c0-6bd6-4a7b-a2f2-7436c81fe0b7
📒 Files selected for processing (6)
.agents/skills/uloop-screenshot/references/annotated-elements.md.claude/skills/uloop-screenshot/references/annotated-elements.mdAssets/Tests/Editor/RaycastGridAnnotatorTests.csPackages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.csPackages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastHitClusterer.csPackages/src/Editor/FirstPartyTools/Screenshot/Skill/references/annotated-elements.md
|
Thanks for the review. Declining the |
Summary
AnnotatedElementsentries when UI occlusion splits its reachable samples into disjoint on-screen regions.SimX/SimY, and outline segments, so agents can address each region independently.User Impact
Before: when a UI panel carved a Physics collider's visible surface into two disconnected clusters,
screenshot --annotate-raycast-gridmerged them into oneAnnotatedElementsentry and drew a single label near one of the regions. The other region had no label or clickable coordinates the agent could target.After: every visually closed region gets its own entry with independent
Label,Bounds,SimX,SimY, andRaycastOutlineSegments. Shared metadata (Path,Name,Layer,Components) still identifies the underlying GameObject, sosimulate-mouse-ui --target-pathkeeps working when the specific region does not matter. Labels are still a singleR1/R2/R3... sequence.Changes
RaycastHitClusterer.SplitIntoConnectedComponents: pure function that partitions a reachable sample list into 4-connected components. Samples without a grid cell become their own components before the BFS runs so a duplicate-cell assert cannot misfire on default(0, 0)positions.RaycastGridAnnotator.CreateComponentElements: extracts oneUIElementInfoper component with a fully deterministic ordering key: minInputY, then minInputX, then lexicographic min(Row, Column)as the tiebreaker soList<T>.Sort's instability cannot flip labels between runs.CollectPhysicsColliderElementsnow callsCreateComponentElementsfor each reachable cluster and range-appends the result.CreateClusterKeycomment updated in English to reflect the new "cluster by GameObject, annotate per closed region" invariant.annotated-elements.md(canonical +.claude/.agentsmirrors) documents the per-region entry behavior and theSimX/SimYvs--target-pathoperational choice.Verification
dist/darwin-arm64/uloop compile— 0 errors, 0 warnings.uloop run-tests RaycastGridAnnotatorTests— 39/39 passing (32 existing + 7 new: five forSplitIntoConnectedComponentscovering L-shape, two-region split, diagonal-only, no-grid-cell, empty input; two forCreateComponentElementscovering shared-metadata / distinct-Bounds-Sim and the deterministic ordering tiebreaker).uloop run-tests ScreenshotUseCaseTests— 4/4 passing.uloop run-tests DefaultToolsCatalogDriftTests— 1/1 passing.RaycastAnnotationPerspectiveDemoScene:PerspectiveSplitGridproduces two entries with the samePathbut distinctBoundsandSimX/SimY(R2 top L-shape, R3 bottom cluster).RaycastLayerSummariesandRaycastLayerNamesCheckedunchanged from the previous PR.