a-b-street/abstreet · warning

A merged perimeter couldn't be blockified

Error message

A merged perimeter couldn't be blockified: {}. {:?}

What it means

After merging two perimeters, the library runs check_continuity to verify the resulting road sequence still forms a connected loop. If the merged perimeter is broken (adjacent roads don't share endpoints), the merge is rejected with this error, including the continuity error and the perimeter in the message.

Solutions

  1. Inspect the printed perimeter to find the two adjacent roads lacking a shared endpoint.
  2. Reverse one of the input perimeters before merging if winding order is mismatched (see Should we reverse one perimeter to match the winding order).
  3. Re-generate blocks from a freshly imported map; corrupted road orderings often stem from stale cached blockfinding data.
  4. If maintaining the code, fix the merge to interleave roads (not just concatenate) when perimeters were split mid-block.
Defensive patterns

Strategy: try-catch

Validate before calling

let ordered = perimeter.roads.windows(2).all(|w| map.get_r(w[0].road).common_endpoint(map.get_r(w[1].road)) != CommonEndpoint::None);

Try / catch

if let Err(e) = perimeter.check_continuity(map) {
    log::debug!("skipping non-contiguous perimeter: {}", e);
    return Ok(None);
}

Prevention

When it happens

Trigger: Perimeter::try_to_merge succeeds in joining two perimeters' road lists, but check_continuity fails because the concatenation order leaves a gap between adjacent roads.

Common situations: Block generation on complex intersections where perimeters interleave; merging perimeters built from non-adjacent road sets; stale map data producing non-contiguous road orderings.

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/b1e70a29d457ff19. Report an issue: GitHub.

Appendix: source

Thrown at blockfinding/src/lib.rs:326

                }
                bail!("Two perimeters had every road in common: {:?}", common);
            }

            self.interior.extend(common);
            self.interior.append(&mut other.interior);

            // Restore the first=last invariant
            self.restore_invariant();

            // Make sure we didn't wind up with any internal dead-ends
            self.collapse_deadends();

            if let Err(err) = self.check_continuity(map) {
                debug!(
                    "A merged perimeter couldn't be blockified: {}. {:?}",
                    err, self
                );
                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]);
            }

View on GitHub (pinned to 0964f29315)