a-b-street/abstreet · error

speed limit has changed

Error message

{:?} speed limit has changed

What it means

While upgrading old edits, a ChangeSpeedLimit command records the previous speed limit (obj.old). The upgrade verifies it still matches the road's current speed_limit; if not, the edit was based on a state that no longer exists, so it aborts rather than applying a change with the wrong baseline.

Solutions

  1. Update the command's `old` field in the edit JSON to the road's current speed limit.
  2. Remove the ChangeSpeedLimit command and re-apply the speed limit change in the app.
  3. Regenerate the map so the speed limit matches the edit's recorded old value.

Example fix

// before (edit JSON)
{"ChangeSpeedLimit": {"id": 42, "old": 30.0, "new": 20.0}}
// after: update `old` to the current limit
{"ChangeSpeedLimit": {"id": 42, "old": 25.0, "new": 20.0}}
Defensive patterns

Strategy: validation

Validate before calling

// confirm the recorded old speed limit matches the current road
let r = map.find_r_by_osm_id(cmd.id)?;
if map.get_r_edit(r).speed_limit != cmd.old { /* update `old` or drop cmd */ }

Try / catch

match Edits::load_from_bytes(map, bytes) {
    Ok(e) => Ok(e),
    Err(err) if err.to_string().contains("speed limit has changed") => {
        Err(anyhow!("edit baseline stale: {}", err))
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: Loading a saved edit whose ChangeSpeedLimit.old differs from road.speed_limit in the current map — e.g. the basemap was regenerated (different OSM-derived defaults) or the limit was already changed.

Common situations: Re-importing a map after OSM data updates changed default speed limits; loading edits saved against an older map build; applying an edit file twice.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at map_model/src/edits/compat.rs:249

            let dst_i = map.find_i_by_osm_id(obj.dst_i)?;
            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
            let edits_dir = if dst_i == map.get_r(r).dst_i {
                Direction::Fwd
            } else if dst_i == map.get_r(r).src_i {
                Direction::Back
            } else {
                bail!("{:?}'s road doesn't point to dst_i at all", obj);
            };
            if road.lanes_ltr[idx].dir == edits_dir {
                bail!("{:?}'s road already points to dst_i", obj);
            }
            road.lanes_ltr[idx].dir = edits_dir;
        } else if let Some(obj) = cmd.remove("ChangeSpeedLimit") {
            let obj: ChangeSpeedLimit = serde_json::from_value(obj).unwrap();
            let r = map.find_r_by_osm_id(obj.id)?;
            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
            if road.speed_limit != obj.old {
                bail!("{:?} speed limit has changed", obj);
            }
            road.speed_limit = obj.new;
        } else if let Some(obj) = cmd.remove("ChangeAccessRestrictions") {
            let obj: ChangeAccessRestrictions = serde_json::from_value(obj).unwrap();
            let r = map.find_r_by_osm_id(obj.id)?;
            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
            if road.access_restrictions != obj.old {
                bail!("{:?} access restrictions have changed", obj);
            }
            road.access_restrictions = obj.new.clone();
        } else {
            commands.push(orig);
        }
    }

    for (r, new) in modified {
        let old = map.get_r_edit(r);
        commands

View on GitHub (pinned to 0964f29315)