a-b-street/abstreet · error

modify_step broke total_length, it's now

Error message

modify_step broke total_length, it's now {}

What it means

After replacing a step, modify_step recomputes total_length by adding the new step's polyline length; because the accumulation effectively assumes the old step's contribution was already netted out by the caller's edits, an inconsistent sequence of edits can drive total_length negative. The method panics when that internal invariant (total_length >= 0) is violated, indicating modify_step broke the path's bookkeeping.

Solutions

  1. Rebuild the path from scratch instead of repeatedly patching steps when many edits accumulate
  2. After each modify_step, recompute total_length from the full steps list to prevent drift
  3. Check that replacement steps have physically plausible polyline lengths before applying
  4. Call get_length()/recalculate total_length periodically and rebuild when it approaches zero

Example fix

// before
path.modify_step(idx, new_step, map); // called in a loop, total_length drifts
// after
path.modify_step(idx, new_step, map);
path.total_length = path.steps.iter().map(|s| s.as_traversable().get_polyline(map).length()).sum();
assert!(path.total_length >= Distance::ZERO);
Defensive patterns

Strategy: validation

Validate before calling

fn total_length(map: &Map, path: &Path) -> Distance {
    path.steps.iter().map(|s| s.as_traversable().get_polyline(map).length()).sum()
}
// call after edits; rebuild if it drifts or goes negative

Try / catch

// panics are not catchable; keep the invariant yourself:
path.modify_step(idx, new_step, map);
if path.get_length() < Distance::ZERO {
    path = rebuild_path(map);
}

Prevention

When it happens

Trigger: Repeated modify_step calls replacing long steps with short ones (or negative-length arithmetic on edited steps) until cumulative total_length drops below Distance::ZERO.

Common situations: Iterative path editing/rerouting loops that shrink path segments progressively; simulation tools that patch many steps per tick; callers replacing steps with polylines whose lengths are inconsistent with the original path.

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

Appendix: source

Thrown at map_model/src/pathfind/v1.rs:346

        // When replacing a turn, also update any references to it in uber_turns
        if let PathStep::Turn(old_turn) = self.steps[idx] {
            for uts in &mut self.uber_turns {
                if let Some(turn_idx) = uts.path.iter().position(|i| i == &old_turn) {
                    if let PathStep::Turn(new_turn) = step {
                        uts.path[turn_idx] = new_turn;
                    } else {
                        panic!("expected turn, but found {:?}", step);
                    }
                }
            }
        }

        self.steps[idx] = step;
        self.total_length += self.steps[idx].as_traversable().get_polyline(map).length();

        if self.total_length < Distance::ZERO {
            panic!(
                "modify_step broke total_length, it's now {}",
                self.total_length
            );
        }
    }

    pub fn current_step(&self) -> PathStep {
        self.steps[0]
    }

    pub fn next_step(&self) -> PathStep {
        self.steps[1]
    }
    pub fn maybe_next_step(&self) -> Option<PathStep> {
        if self.is_last_step() {
            None
        } else {
            Some(self.next_step())

View on GitHub (pinned to 0964f29315)