a-b-street/abstreet · warning
Part of the perimeter goes from
Error message
Part of the perimeter goes from {:?} to {:?}, but they don't share a common endpoint What it means
check_continuity walks the perimeter's road list pairwise and requires each consecutive pair of roads to share a common endpoint. If two adjacent roads in the perimeter don't touch, the perimeter isn't a valid closed ring and cannot form a block, so this error bails with the two offending roads.
Solutions
- Look at the two roads named in the message and verify they meet at a shared intersection in the map.
- Regenerate the map/blocks from current OSM data — stale imports can contain disconnected roads.
- Sort/reorder the perimeter roads so consecutive entries are actually adjacent before calling check_continuity.
- Report/fix perimeter construction so roads are stored in ring order.
Defensive patterns
Strategy: validation
Validate before calling
fn is_contiguous(perimeter: &Perimeter, map: &Map) -> bool {
perimeter.roads.windows(2).all(|w| {
map.get_r(w[0].road).common_endpoint(map.get_r(w[1].road)) != CommonEndpoint::None
})
} Prevention
- Only build perimeters from roads ordered as a connected ring
- Check road connectivity after any map re-import
- Run check_continuity as a precondition before converting to blocks
When it happens
Trigger: Any perimeter whose roads vector has two consecutive entries (pair[i], pair[i+1]) with CommonEndpoint::None, checked after merging perimeters in try_to_merge.
Common situations: Map data where roads near an intersection don't geometrically connect; perimeter construction bugs that order roads incorrectly; merging perimeters split at intersections.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Two perimeters had every road in common
- A merged perimeter couldn't be blockified
- and don't share a common endpoint
- ( ) is a border, but is connected to >1 road
- isn't an endpoint of
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/f0b205f3dd86fb7e.
Report an issue: GitHub.
Appendix: source
Thrown at blockfinding/src/lib.rs:343
);
bail!(
"A merged perimeter couldn't be blockified: {}. {:?}",
err,
self
);
}
return Ok(());
}
unreachable!()
}
fn check_continuity(&self, map: &Map) -> Result<()> {
for pair in self.roads.windows(2) {
let r1 = map.get_r(pair[0].road);
let r2 = map.get_r(pair[1].road);
if r1.common_endpoint(r2) == CommonEndpoint::None {
bail!("Part of the perimeter goes from {:?} to {:?}, but they don't share a common endpoint", pair[0], pair[1]);
}
}
Ok(())
}
/// Should we reverse one perimeter to match the winding order?
///
/// This is only meant to be called in the middle of try_to_merge. It assumes both perimeters
/// have already been rotated so the common roads are at the end. The invariant of first=last
/// is not true.
fn reverse_to_fix_winding_order(&self, map: &Map, other: &Perimeter) -> bool {
// Using geometry to determine winding order is brittle. Look for any common road, and see
// where it points.
let common_example = self.roads.last().unwrap().road;
let last_common_for_self = match map
.get_r(common_example)
.common_endpoint(map.get_r(wraparound_get(&self.roads, self.roads.len() as isize).road))
{View on GitHub (pinned to 0964f29315)