a-b-street/abstreet · error
and don't share a common endpoint
Error message
{} and {} don't share a common endpoint What it means
to_block converts a perimeter into a Block by tracing its boundary lanes; it needs each consecutive pair of lanes to share a common endpoint to stitch the polylines together. When CommonEndpoint::None, the two lanes are disjoint, so the block cannot be built and this error names both lane IDs.
Solutions
- Verify the two lane IDs from the message actually meet at a shared intersection in the current map.
- Build the Perimeter through the normal blockfinding pipeline rather than hand-constructing it.
- Re-run find_blocks on the latest map to refresh cached blocks after road edits.
- If maintaining the code, ensure the correct CommonEndpoint variant (FirstFirst/FirstLast/...) is handled so orientation is fixed before tracing.
Defensive patterns
Strategy: validation
Validate before calling
if perimeter.roads.windows(2).any(|w| {
map.get_r(w[0].road).common_endpoint(map.get_r(w[1].road)) == CommonEndpoint::None
}) {
return Err(anyhow!("perimeter has disjoint adjacent roads"));
} Prevention
- Construct perimeters via the blockfinding pipeline, not by hand
- Refresh blocks after road network changes
- Validate all adjacent lane pairs share an endpoint before to_block
When it happens
Trigger: Calling Perimeter::to_block (public API) on a perimeter whose step trace encounters two adjacent lanes with no shared endpoint, during block conversion.
Common situations: Calling to_block on a perimeter you built by hand instead of via blockfinding; map edits that disconnected lanes after blocks were found; side-of-driving or step-direction logic misidentifying the shared corner.
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
- isn't an endpoint of
- Two perimeters had every road in common
- A merged perimeter couldn't be blockified
- Part of the perimeter goes from
- ( ) is a border, but is connected to >1 road
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/356e8554c1476d4a.
Report an issue: GitHub.
Appendix: source
Thrown at blockfinding/src/lib.rs:644
}
let keep_lane_orientation = if pair[0].road == pair[1].road {
// We're doubling back at a dead-end. Always follow the orientation of the lane.
true
} else {
match lane1.common_endpoint(lane2) {
CommonEndpoint::One(i) => i == lane1.dst_i,
CommonEndpoint::Both => {
// Two different roads link the same two intersections. I don't think we
// can decide the order of points other than seeing which endpoint is
// closest to our last point.
if let Some(last) = pts.last() {
last.dist_to(pl.first_pt()) < last.dist_to(pl.last_pt())
} else {
// The orientation doesn't matter
true
}
}
CommonEndpoint::None => bail!(
"{} and {} don't share a common endpoint",
lane1.id,
lane2.id
),
}
};
if !keep_lane_orientation {
pl = pl.reversed();
}
// Before we add this road's points, try to trace along the polygon's boundary. Usually
// this has no effect (we'll dedupe points), but sometimes there's an extra curve.
//
// Note this logic is similar to how we find SharedSidewalkCorners. Don't rely on that
// existing, since the outermost lane mightn't be a sidewalk.
//
// If the ring.doubles_back(), don't bother. If we tried to trace the boundary, it
// usually breaks the final Ring we produce. Better to skip bad intersection polygonsView on GitHub (pinned to 0964f29315)