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

  1. Look at the two roads named in the message and verify they meet at a shared intersection in the map.
  2. Regenerate the map/blocks from current OSM data — stale imports can contain disconnected roads.
  3. Sort/reorder the perimeter roads so consecutive entries are actually adjacent before calling check_continuity.
  4. 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

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


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)