Skip to content

feat: Raycast annotation now labels every UI-split closed region separately - #1667

Merged
hatayama merged 2 commits into
v3-betafrom
annotate-per-outline-component
Jul 9, 2026
Merged

feat: Raycast annotation now labels every UI-split closed region separately#1667
hatayama merged 2 commits into
v3-betafrom
annotate-per-outline-component

Conversation

@hatayama

@hatayama hatayama commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • A single PhysicsCollider GameObject can now surface as multiple AnnotatedElements entries when UI occlusion splits its reachable samples into disjoint on-screen regions.
  • Each closed region gets its own label, bounds, 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-grid merged them into one AnnotatedElements entry 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, and RaycastOutlineSegments. Shared metadata (Path, Name, Layer, Components) still identifies the underlying GameObject, so simulate-mouse-ui --target-path keeps working when the specific region does not matter. Labels are still a single R1/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 one UIElementInfo per component with a fully deterministic ordering key: min InputY, then min InputX, then lexicographic min (Row, Column) as the tiebreaker so List<T>.Sort's instability cannot flip labels between runs.
  • CollectPhysicsColliderElements now calls CreateComponentElements for each reachable cluster and range-appends the result.
  • CreateClusterKey comment updated in English to reflect the new "cluster by GameObject, annotate per closed region" invariant.
  • annotated-elements.md (canonical + .claude / .agents mirrors) documents the per-region entry behavior and the SimX/SimY vs --target-path operational choice.

Verification

  • dist/darwin-arm64/uloop compile — 0 errors, 0 warnings.
  • uloop run-tests RaycastGridAnnotatorTests — 39/39 passing (32 existing + 7 new: five for SplitIntoConnectedComponents covering L-shape, two-region split, diagonal-only, no-grid-cell, empty input; two for CreateComponentElements covering shared-metadata / distinct-Bounds-Sim and the deterministic ordering tiebreaker).
  • uloop run-tests ScreenshotUseCaseTests — 4/4 passing.
  • uloop run-tests DefaultToolsCatalogDriftTests — 1/1 passing.
  • Live smoke on RaycastAnnotationPerspectiveDemoScene: PerspectiveSplitGrid produces two entries with the same Path but distinct Bounds and SimX/SimY (R2 top L-shape, R3 bottom cluster). RaycastLayerSummaries and RaycastLayerNamesChecked unchanged from the previous PR.

Review in cubic

hatayama added 2 commits July 10, 2026 01:51
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).
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

PhysicsCollider region-splitting

Layer / File(s) Summary
Connected-component BFS splitter
Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastHitClusterer.cs
Adds SplitIntoConnectedComponents, grouping reachable samples via BFS over 4-neighbor grid adjacency, with singleton components for samples lacking grid coordinates.
Per-component element creation
Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.cs
CollectPhysicsColliderElements now emits one UIElementInfo per connected component via new CreateComponentElements, with deterministic ordering (CompareComponentsByTopLeft, MinInputX, MinInputY, MinRowColumn) and an updated CreateClusterKey comment.
Unit tests
Assets/Tests/Editor/RaycastGridAnnotatorTests.cs
New tests cover 4-connected merging, disconnected regions, diagonal non-adjacency, missing grid-cell handling, empty input, multi-entry CreateComponentElements output, and lexicographic tie-break label ordering.
Documentation updates
.agents/skills/uloop-screenshot/references/annotated-elements.md, .claude/skills/uloop-screenshot/references/annotated-elements.md, Packages/src/Editor/FirstPartyTools/Screenshot/Skill/references/annotated-elements.md
Adds "PhysicsCollider Entries Per Closed Region" sections explaining shared vs. region-specific fields and how to target a specific region.

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
Loading

Possibly related PRs

  • hatayama/unity-cli-loop#1502: Overlaps with outline-segment generation and RaycastGridAnnotator.CreatePhysicsColliderElement attaching outline segments, directly relevant to the region-specific RaycastOutlineSegments added here.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: raycast annotations now split and label each closed UI region separately.
Description check ✅ Passed The description is directly related to the changes and accurately summarizes the behavior, implementation, and verification.
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 annotate-per-outline-component

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.

🧹 Nitpick comments (1)
Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.cs (1)

192-216: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse the duplicated MinInputX/MinInputY scanners.

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

📥 Commits

Reviewing files that changed from the base of the PR and between a92a8fa and a58dfef.

📒 Files selected for processing (6)
  • .agents/skills/uloop-screenshot/references/annotated-elements.md
  • .claude/skills/uloop-screenshot/references/annotated-elements.md
  • Assets/Tests/Editor/RaycastGridAnnotatorTests.cs
  • Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastGridAnnotator.cs
  • Packages/src/Editor/FirstPartyTools/Screenshot/RaycastAnnotation/RaycastHitClusterer.cs
  • Packages/src/Editor/FirstPartyTools/Screenshot/Skill/references/annotated-elements.md

@hatayama

hatayama commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the review.

Declining the MinInputX/MinInputY consolidation nitpick. The two helpers are each ten lines of self-contained code where the method name (MinInputX, MinInputY) tells the caller exactly which coordinate is being scanned. Consolidating into MinInput(samples, s => s.InputX) trades that call-site clarity for a small DRY win and adds a System.Func<...> abstraction that would be the only such delegate use in this file. CodeRabbit itself flagged this as "Trivial / Low value", and the project style guide prefers call-site readability over minimizing duplication in short private helpers, so keeping the two named methods as-is.

@hatayama
hatayama merged commit 1444de6 into v3-beta Jul 9, 2026
10 checks passed
@hatayama
hatayama deleted the annotate-per-outline-component branch July 9, 2026 17:01
@github-actions github-actions Bot mentioned this pull request Jul 11, 2026
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