a-b-street/abstreet · error

Can't start at ; it's the edge of a border already

Error message

Can't start at {}; it's the edge of a border already

What it means

A spawn whose start position is exactly at the end of its goal border lane means the trip is already finished at distance zero. into_plan treats this degenerate trip (start == end at a border edge) as a specification bug and panics.

Solutions

  1. Nudge the spawn dist_along back from the lane end (spawn strictly inside the map)
  2. Reject or resample such spawns in the scenario generator
  3. Choose a different border/lane as the trip goal

Example fix

// before
let start = Position::new(border_lane, lane_length);
// after
let start = Position::new(border_lane, lane_length - Distance::meters(1.0));
Defensive patterns

Strategy: validation

Validate before calling

if let DrivingGoal::Border(_, end_lane) = goal {
    if start_pos.lane() == *end_lane
        && start_pos.dist_along() == map.get_l(*end_lane).length()
    {
        return None; // degenerate trip
    }
}

Try / catch

let plan = std::panic::catch_unwind(|| spec.into_plan(&map, &mut rng));

Prevention

When it happens

Trigger: into_plan() with a DrivingGoal::Border whose end_lane equals start_pos.lane() and start_pos.dist_along() equals that lane's full length — the agent spawns at the very border it aims to reach.

Common situations: Scenario generators sampling spawn positions uniformly along a border lane and hitting exactly lane end; agents intended to enter the map that are instead placed on the exit border.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at sim/src/make/spawner.rs:78

impl TripSpec {
    pub fn into_plan(self, map: &Map) -> (TripSpec, Vec<TripLeg>) {
        // TODO We'll want to repeat this validation when we spawn stuff later for a second leg...
        let mut legs = Vec::new();
        match &self {
            TripSpec::VehicleAppearing {
                start_pos,
                goal,
                use_vehicle,
                ..
            } => {
                if start_pos.dist_along() >= map.get_l(start_pos.lane()).length() {
                    panic!("Can't spawn at {}; it isn't that long", start_pos);
                }
                if let DrivingGoal::Border(_, end_lane) = goal {
                    if start_pos.lane() == *end_lane
                        && start_pos.dist_along() == map.get_l(*end_lane).length()
                    {
                        panic!(
                            "Can't start at {}; it's the edge of a border already",
                            start_pos
                        );
                    }
                }

                let constraints = if use_vehicle.vehicle_type == VehicleType::Bike {
                    PathConstraints::Bike
                } else {
                    PathConstraints::Car
                };

                legs.push(TripLeg::Drive(*use_vehicle, goal.clone()));
                if let DrivingGoal::ParkNear(b) = goal {
                    legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
                }

                if goal.goal_pos(constraints, map).is_none() {

View on GitHub (pinned to 0964f29315)