a-b-street/abstreet · error
No common roads
Error message
No common roads
What it means
Perimeter::try_to_merge merges two adjacent block perimeters, which requires them to share at least one road. The two perimeters' road sets are disjoint, so no merge is possible and the function bails (optionally warning first when debug_failures is set).
Solutions
- Pre-check that the two perimeters share a road before calling try_to_merge and skip non-adjacent pairs.
- Verify the blocks really are neighbors; only merge blocks produced from the same map and same skip configuration.
- If debug_failures was enabled, read the warning output to identify which block pair is disjoint and exclude it upstream.
Example fix
// before
merged = a.try_to_merge(map, b, &mut small_roads, debug_failures)?;
// after
let a_roads: HashSet<RoadID> = a.roads.iter().map(|id| id.road).collect();
let b_roads: HashSet<RoadID> = b.roads.iter().map(|id| id.road).collect();
if a_roads.is_disjoint(&b_roads) { continue; } // not adjacent, skip
merged = a.try_to_merge(map, b, &mut small_roads, debug_failures)?; Defensive patterns
Strategy: validation
Validate before calling
let a_set: HashSet<RoadID> = a.roads.iter().map(|rs| rs.road).collect(); let b_set: HashSet<RoadID> = b.roads.iter().map(|rs| rs.road).collect(); anyhow::ensure!(!a_set.is_disjoint(&b_set), "perimeters not adjacent");
Type guard
fn perimeters_adjacent(a: &Perimeter, b: &Perimeter) -> bool {
let a_set: HashSet<RoadID> = a.roads.iter().map(|rs| rs.road).collect();
b.roads.iter().any(|rs| a_set.contains(&rs.road))
} Prevention
- Only attempt merges between blocks from the same map and skip configuration.
- Pre-filter block pairs by road overlap before merging.
When it happens
Trigger: Calling try_to_merge(other) on a Perimeter whose roads share no RoadID with `other`'s roads — the intersection of the two RoadID sets is empty.
Common situations: Block merging passes where blocks that merely touch at a point (or are separated by an untraced/skipped road) are fed into the merger; running merging after changing the skip set so previously adjacent blocks no longer overlap.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- Rotating against common infinite-looped
- The common roads on the first aren't consecutive, near
- Started on a road we shouldn't trace
- Starting on inner piece of a loop road
- hit the map boundary at
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/8c8f535488e23f29.
Report an issue: GitHub.
Appendix: source
Thrown at blockfinding/src/lib.rs:209
fn try_to_merge(
&mut self,
map: &Map,
other: &mut Perimeter,
debug_failures: bool,
) -> Result<()> {
for reverse_to_fix_winding_order in [false, true] {
self.undo_invariant();
other.undo_invariant();
// Calculate common roads
let roads1: HashSet<RoadID> = self.roads.iter().map(|id| id.road).collect();
let roads2: HashSet<RoadID> = other.roads.iter().map(|id| id.road).collect();
let common: HashSet<RoadID> = roads1.intersection(&roads2).cloned().collect();
if common.is_empty() {
if debug_failures {
warn!("No common roads");
}
bail!("No common roads");
}
// "Rotate" the order of roads, so that all of the overlapping roads are at the end of the
// list. If the entire perimeter is surrounded by the other, then no rotation needed.
if self.roads.len() != common.len() {
let mut i = 0;
while common.contains(&self.roads[0].road)
|| !common.contains(&self.roads.last().unwrap().road)
{
self.roads.rotate_left(1);
i += 1;
if i == self.roads.len() {
bail!(
"Rotating {:?} against common {:?} infinite-looped",
self.roads,
common
);View on GitHub (pinned to 0964f29315)