a-b-street/abstreet · error
This intersection doesn't have 2 stages.
Error message
This intersection doesn't have 2 stages.
What it means
TrafficSignal::adjust_major_minor_timing rewrites a two-stage signal so the major road gets `major` time and the minor road `minor` time. It requires exactly two stages; if the signal has any other number of stages the transformation is undefined and the method bails before touching anything.
Solutions
- Check signal.stages.len() == 2 before calling and skip non-2-stage signals
- Use a signal-retiming API suited to multi-stage signals instead
- Regenerate a simple 2-stage assignment first if a major/minor scheme is really appropriate
- Filter target intersections (e.g. minor 4-way stops) where the default 2-stage signal applies
Example fix
// before
signal.adjust_major_minor_timing(major, minor, map)?;
// after
if signal.stages.len() == 2 {
signal.adjust_major_minor_timing(major, minor, map)?;
} Defensive patterns
Strategy: validation
Validate before calling
if signal.stages.len() != 2 {
return Err(anyhow!("signal {} has {} stages, expected 2", signal.id, signal.stages.len()));
} Type guard
fn is_two_stage(signal: &TrafficSignal) -> bool {
signal.stages.len() == 2
} Try / catch
if is_two_stage(signal) {
signal.adjust_major_minor_timing(major, minor, map)?;
} else {
skip_and_log(signal.id);
} Prevention
- Check stage count before applying major/minor retiming
- Restrict retiming scripts to intersections known to use the default 2-stage signal
- Regenerate the default signal when stage counts diverge from expectations
When it happens
Trigger: Calling adjust_major_minor_timing on a signal with 1, 3, or more stages — e.g. complex intersections with dedicated turn stages, or after a prior transformation changed the stage count.
Common situations: Applying a major/minor retiming script uniformly to all signals, some of which are multi-stage; editing an intersection that gained turn lanes and stages since the script was written.
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
- This intersection doesn't have major/minor roads; they're…
- This intersection doesn't use fixed timing.
- This intersection didn't already group major/minor roads…
- isn't a traffic signal
- Traffic signal assignment for
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/15e220a906c17125.
Report an issue: GitHub.
Appendix: source
Thrown at map_model/src/objects/traffic_signals.rs:215
if !has_all_walk {
self.stages.push(all_walk_stage);
}
self != &orig
}
/// Modifies the fixed timing of all stages, applying either a major or minor duration,
/// depending on the relative rank of the roads involved in the intersection. If this
/// transformation couldn't be applied, returns an error. Even if an error is returned, the
/// signal may have been changed -- so only call this on a cloned signal.
pub fn adjust_major_minor_timing(
&mut self,
major: Duration,
minor: Duration,
map: &Map,
) -> Result<()> {
if self.stages.len() != 2 {
bail!("This intersection doesn't have 2 stages.");
}
// What's the rank of each road?
let mut rank_per_road: BTreeMap<RoadID, usize> = BTreeMap::new();
for r in &map.get_i(self.id).roads {
rank_per_road.insert(*r, map.get_r(*r).get_detailed_rank());
}
let mut ranks: Vec<usize> = rank_per_road.values().cloned().collect();
ranks.sort_unstable();
ranks.dedup();
if ranks.len() == 1 {
bail!("This intersection doesn't have major/minor roads; they're all the same rank.");
}
let highest_rank = ranks.pop().unwrap();
// Try to apply the transformation
let orig = self.clone();
for stage in &mut self.stages {View on GitHub (pinned to 0964f29315)