a-b-street/abstreet · error

Stop sign has roads now, but from edits

Error message

Stop sign has {} roads now, but {} from edits

What it means

During permanent-edit application, every road adjacent to a stop-sign intersection that was listed in the edit must still exist on the intersection after permanent map changes are applied. ControlStopSign::new rebuilds the sign's road set from the current map; if the count of edited roads differs from the rebuilt set's size, some edited road is no longer adjacent, and the library bails instead of silently dropping edits.

Solutions

  1. Re-save the stop-sign edit against the current map: open the edits in the UI and re-confirm the stop sign so the road list matches the intersection's current roads.
  2. Check whether the permanent edits changed the intersection's roads (deleted/merged roads) and remove stale entries from the edit's stop-sign road list.
  3. Regenerate the map and recreate the edit if the underlying OSM data changed significantly.

Example fix

// before
let edit = EditIntersectionControl::StopSign(stop_sign_from_old_map);
map.maybe_edit_i(i, edit, &mut orig_edits)?;
// after
// rebuild the stop sign from the current intersection's roads
let ss = ControlStopSign::new(map, i);
let mut ss = ss;
for (r, stop) in translated_must_stop {
    if let Some(road) = ss.roads.get_mut(&r) { road.must_stop = stop; }
}
map.maybe_edit_i(i, EditIntersectionControl::StopSign(ss), &mut orig_edits)?;
Defensive patterns

Strategy: validation

Validate before calling

let ss = ControlStopSign::new(&map, i);
let all_roads_known = edit_roads.iter().all(|r| ss.roads.contains_key(r));
if !all_roads_known { /* repair or drop the stop-sign edit */ }

Try / catch

match edits.to_permanent(&mut map) {
    Ok(_) => {},
    Err(e) if e.to_string().contains("roads now") => regenerate_stop_sign_edits(&map),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling Edits::to_permanent / with_permanent on an edit containing an EditIntersectionControl::StopSign whose translated_must_stop map references a road that, after the permanent edit was applied (e.g. a road was deleted or merged), is no longer connected to the intersection.

Common situations: Applying an edit JSON made against an older/modified map where the stop-sign intersection's geometry changed; editing a save file after a map regeneration; merging edits that delete roads adjacent to stop signs.

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

Appendix: source

Thrown at map_model/src/edits/perma.rs:270

                .map(|(id, turn_type)| (id.to_movement(map).to_permanent(map), *turn_type))
                .collect(),
        }
    }
}

impl PermanentEditIntersection {
    fn with_permanent(self, i: IntersectionID, map: &Map) -> Result<EditIntersection> {
        let control = match self.control {
            PermanentEditIntersectionControl::StopSign { must_stop } => {
                let mut translated_must_stop = BTreeMap::new();
                for (r, stop) in must_stop {
                    translated_must_stop.insert(map.find_r_by_osm_id(r)?, stop);
                }

                // Make sure the roads exactly match up
                let mut ss = ControlStopSign::new(map, i);
                if translated_must_stop.len() != ss.roads.len() {
                    bail!(
                        "Stop sign has {} roads now, but {} from edits",
                        ss.roads.len(),
                        translated_must_stop.len()
                    );
                }
                for (r, stop) in translated_must_stop {
                    if let Some(road) = ss.roads.get_mut(&r) {
                        road.must_stop = stop;
                    } else {
                        bail!("{} doesn't connect to {}", i, r);
                    }
                }

                EditIntersectionControl::StopSign(ss)
            }
            PermanentEditIntersectionControl::TrafficSignal(ts) => {
                EditIntersectionControl::TrafficSignal(ts)
            }

View on GitHub (pinned to 0964f29315)