a-b-street/abstreet · error

Looped back on the same road, but not at a dead-end

Error message

Looped back on the same road, but not at a dead-end

What it means

During the perimeter walk, the trace stepped from an intersection back onto the same road side it arrived on, and even after stepping one further around the sorted road sides it's still on the same road. This is only valid at a dead-end intersection (exactly 2 roads left after filtering); otherwise the walking algorithm's assumption is broken, so it bails.

Solutions

  1. Inspect the intersection geometry in the map model for duplicate or zero-length roads and fix the map input.
  2. Add the problematic road(s) to `skip` so the trace takes a different route.
  3. Update the map import ( newer map_model may sort road sides differently); regenerate the map.
Defensive patterns

Strategy: try-catch

Try / catch

match Perimeter::single_block(map, start, &skip) {
    Ok(p) => Some(p),
    Err(e) => { warn!("trace failed from {:?}: {}", start, e); None }
}

Prevention

When it happens

Trigger: single_block reaches an intersection where the clockwise next road side and the one after it both belong to the incoming road, and the filtered sorted_roads list has more than 2 entries — i.e. a same-road wraparound at a non-dead-end intersection.

Common situations: Degenerate OSM geometry: very short dual-carriageway links, slip lanes, or duplicate/parallel roads converging at an intersection; map edits or imports producing overlapping road sides.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/e19108af0f1c96c1. Report an issue: GitHub.

Appendix: source

Thrown at blockfinding/src/lib.rs:90

                bail!("hit the map boundary at {}", i.orig_id);
            }
            let mut sorted_roads = i.get_road_sides_sorted(map);
            sorted_roads.retain(|id| !skip.contains(&id.road));

            let idx = sorted_roads
                .iter()
                .position(|x| *x == current_road_side)
                .unwrap() as isize;
            // Do we go clockwise or counter-clockwise around the intersection? Well, unless we're
            // at a dead-end, we want to avoid the other side of the same road.
            let mut next = *wraparound_get(&sorted_roads, idx + 1);
            assert_ne!(next, current_road_side);
            if next.road == current_road_side.road {
                next = *wraparound_get(&sorted_roads, idx - 1);
                assert_ne!(next, current_road_side);
                if next.road == current_road_side.road {
                    if sorted_roads.len() != 2 {
                        bail!("Looped back on the same road, but not at a dead-end");
                    }
                }
            }
            roads.push(current_road_side);
            current_road_side = next;
            current_intersection = map
                .get_r(current_road_side.road)
                .other_endpt(current_intersection);

            if current_road_side == start_road_side {
                roads.push(start_road_side);
                break;
            }

            if roads.len() > map.all_roads().len() {
                bail!(
                    "Infinite loop starting from {start} ({})",
                    map.get_parent(start).orig_id

View on GitHub (pinned to 0964f29315)