Skip to content

Find the nearest floor edge without reading every edge - #254

Merged
SunkenInTime merged 3 commits into
mainfrom
t3code/web-snap-speed
Oct 6, 2026
Merged

SunkenInTime merged 3 commits into
mainfrom
t3code/web-snap-speed

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

On the web, keeping an agent out of walls while it's dragged cost 384 µs per drag frame. It now costs 83 µs, and agents land on exactly the same points as before.

What was slow

SvgHeightVisibility.standablePointNear runs on every drag frame. It moves the agent's cone out of wall ink and back onto the floor. When the point was just off the floor, _pulledIn looked for the nearest floor edge by measuring every edge of the floor outline: 9,000–13,500 edges per map, with an Offset allocation and a modulo per edge. On the web, where there's no native library, that was 5.7% of busy time in a browser CPU profile of a cone drag (Lotus, Bind, Ascent, Breeze, 8 s each).

What changed

The floor footprint already files its edges into 1-unit rows for its containment test. The nearest-edge search now reads those rows outward from the point's own row. It stops once the nearest edge found is no farther than the rows not yet read. An unread edge is more than that far away, because rows are filed with a 0.05 margin, so the stop is exact. Ties still go to the first edge in ring order. A NaN, infinite or huge point still scans every edge in order, as before. Edge endpoints now live in one Float64List that both the containment test and the search read, with no per-edge Offset or %.

Numbers

before after
Browser drag profile, per frame 384 µs (5.7% of busy) 83 µs (1.3%)
Browser, per call, mean / p99 190–310 / 575–935 µs 10–17 / 60–115 µs
VM, per call, mean / p99 170–220 / 790–1,170 µs 10–17 / 80–120 µs

Desktop runs the same Dart, so it gets the same speedup.

Same results

An old-against-new comparison over 299,234 points on all 26 map sides found no difference in the snapped point. The points included drag paths through walls, points beside wall and floor corners, off-map points, and NaN and infinite points; about 30% got moved and 35% returned null. The same comparison inside the browser build found no difference on 8 sides.

What's not covered

  • I measured this on the web drag harness, which drags the real canvas widgets in headless Edge. I didn't re-run the signed-in app for this PR.
  • The remaining 83 µs is mostly the containment test's loop over every wall in _blockingWallAt and the ground-height lookup. Each would be its own change.

Testing

flutter test: 1,763 passed, 9 skipped (the native-library tests that need ICARUS_SVG_NATIVE_LIBRARY). The standable, footprint and vision tests that pin this behaviour pass unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved how nearby points are checked against wall and receiver footprints, including cases where multiple nearby edges need to be considered.
    • Improved footprint containment and edge checks for points with unusually large or non-finite coordinates.

RetriggerConfidence Score: 5/5

No outstanding findings block merging.

Summary

The PR searches footprint rows outward for the nearest floor edge instead of measuring every edge. It now measures each edge at most once per lookup, fixing the previously reported repeated work. No outstanding findings remain.

Reviews (2) · Last reviewed commit: "Measure each floor edge once per nearest..."

Dragging an agent snaps its cone out of wall ink and back onto the floor
(standablePointNear). When the point was off the floor, _pulledIn looked
for the nearest edge of the floor footprint by measuring every edge of it:
9,000 to 13,500 edges on Lotus, Breeze, Bind and Ascent, with an Offset
allocation and a modulo per edge. On the web, where no native library
runs, that cost 384 us per drag frame in a browser CPU profile of the
drag harness (5.7% of busy time).

The footprint already files its edges in rows one unit tall for its
containment test. The nearest-edge search now reads those rows outward
from the point's own and stops once the nearest edge found is no farther
than the rows not yet read; an edge outside them is more than that far.
Ties still go to the first edge in ring order, and a non-finite point
reads every edge in order as before, so the chosen edge, foot and
snapped point are the same. Edge endpoints are kept in one Float64List
that the containment test and the search both read.

Measured on the four maps above:
- Browser drag (CPU profile, 4 maps x 8 s): 384 -> 83 us per frame
  under standablePointNear.
- Browser, per call on drag paths through walls: mean 190-310 -> 10-17
  us, p99 575-935 -> 60-115 us.
- VM, per call: mean 170-220 -> 10-17 us, p99 790-1170 -> 80-120 us.

An old-against-new comparison over 299,234 points on all 26 map sides
(drag paths, points beside wall and floor vertices, off-map, NaN and
infinite points) found no difference in the snapped point.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 38 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2a3d3784-918c-4166-9f72-c595c2207b55
📥 Commits

Reviewing files that changed from the base of the PR and between be6b595 and c0f0f66.

📒 Files selected for processing (2)
  • lib/view_cone/svg_height_visibility.dart
  • test/svg_standable_point_test.dart

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b042448d-d000-4d08-927e-25d91fa105a8
📥 Commits

Reviewing files that changed from the base of the PR and between 0fca2d1 and be6b595.

📒 Files selected for processing (1)
  • lib/view_cone/svg_height_visibility.dart

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change caches footprint edge coordinates and indexes edges by vertical row. It updates containment and nearest-edge queries to use that cache, and changes visibility stepping to pass footprints into edge selection.

Changes

Footprint-based visibility stepping

Layer / File(s) Summary
Cache and query footprint edges
lib/view_cone/svg_height_visibility.dart
_Footprint stores flattened edge endpoints and row buckets. Containment reads the cached endpoints. New helpers calculate edge distance and find the nearest edge.
Select stepped points from footprints
lib/view_cone/svg_height_visibility.dart
_steppedAcross accepts a footprint. Without an acceptance predicate, it tries the nearest edge. With a predicate, it checks edges within reach in distance order. standablePointNear and _pulledIn pass footprints to _steppedAcross.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Merge Risk: ⚪ Minimal · up to be6b5

No concrete issue remains that calls for a change before merging. Normal checks are still appropriate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: finding the nearest floor edge without scanning every edge.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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 unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

At 1e16 scale, rounding let an edge just beyond the rows read compute the
same distance as the one found, and reading every edge would have chosen
it. The search now stops only once the nearest edge is strictly nearer
than the unread rows, with room for rounding. A footprint too tall to
split into rows (coordinates near 1e308) reads every edge, as before,
instead of throwing. Both cases are now in svg_standable_point_test.

Found in review by Astra.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread lib/view_cone/svg_height_visibility.dart
A tall edge is filed in every row it spans, so a point far beside the
floor measured it once per row read: 1,001 times for one edge of a floor
1,000 units tall. Each search now skips edges it has already measured;
the edge it picks is the same.

Found in review by Greptile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SunkenInTime
SunkenInTime merged commit 68cea98 into main Oct 6, 2026
15 checks passed
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