a-b-street/abstreet · error
This intersection didn't already group major/minor roads…
Error message
This intersection didn't already group major/minor roads together.
What it means
After rewriting the two stages, adjust_major_minor_timing requires the original simple_cycle_duration to equal major + minor — i.e. the signal previously grouped all major-road time in one stage and all minor time in the other. If the total doesn't match, the signal wasn't in the expected major/minor grouped form, so it bails (the method restores `orig` semantics by failing before committing unclear changes).
Solutions
- Pass major/minor values matching the signal's current simple_cycle_duration (read it first and derive the split)
- Reset the signal to the default generated assignment before applying major/minor timing
- Verify the two stages actually correspond to major and minor road groups
- Log simple_cycle_duration and adjust the requested values accordingly
Example fix
// before signal.adjust_major_minor_timing(Duration::seconds(30), Duration::seconds(30), map)?; // after let total = signal.simple_cycle_duration(); let major = total * 2 / 3; let minor = total - major; signal.adjust_major_minor_timing(major, minor, map)?;
Defensive patterns
Strategy: validation
Validate before calling
let total = signal.simple_cycle_duration();
if total != major + minor {
return Err(anyhow!("cycle {} != major {} + minor {}", total, major, minor));
} Type guard
fn matches_cycle(signal: &TrafficSignal, major: Duration, minor: Duration) -> bool {
signal.simple_cycle_duration() == major + minor
} Try / catch
if matches_cycle(signal, major, minor) {
signal.adjust_major_minor_timing(major, minor, map)?;
} else {
let (m, n) = split_current_cycle(signal, map);
signal.adjust_major_minor_timing(m, n, map)?;
} Prevention
- Derive major/minor from the existing simple_cycle_duration rather than hardcoding
- Reset to the default generated signal before applying major/minor timing
- Confirm the two stages really group major and minor movements
When it happens
Trigger: Calling adjust_major_minor_timing on a 2-stage fixed signal whose cycle duration != major + minor: e.g. durations previously set to arbitrary values, or stages don't correspond to major/minor grouping (extra crosswalk-only time in the cycle).
Common situations: Signals whose timings were previously hand-tuned individually; applying an assumed (major, minor) pair that doesn't match the existing cycle; crosswalk time altering simple cycle duration.
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 2 stages.
- This intersection doesn't have major/minor roads; they're…
- This intersection doesn't use fixed timing.
- 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/3474dd3433431b16.
Report an issue: GitHub.
Appendix: source
Thrown at map_model/src/objects/traffic_signals.rs:251
for stage in &mut self.stages {
match stage.stage_type {
StageType::Fixed(_) => {}
_ => bail!("This intersection doesn't use fixed timing."),
}
// Ignoring crosswalks, do any of the turns come from a major road?
if stage
.protected_movements
.iter()
.any(|m| !m.crosswalk && highest_rank == rank_per_road[&m.from.road])
{
stage.stage_type = StageType::Fixed(major);
} else {
stage.stage_type = StageType::Fixed(minor);
}
}
if self.simple_cycle_duration() != major + minor {
bail!("This intersection didn't already group major/minor roads together.");
}
if self == &orig {
bail!("This change had no effect.");
}
Ok(())
}
pub fn missing_turns(&self, i: &Intersection) -> BTreeSet<MovementID> {
let mut missing: BTreeSet<MovementID> = i.movements.keys().cloned().collect();
for stage in &self.stages {
for m in &stage.protected_movements {
missing.remove(m);
}
for m in &stage.yield_movements {
missing.remove(m);
}View on GitHub (pinned to 0964f29315)