{"record":{"id":"15e220a906c17125","repo":"a-b-street/abstreet","slug":"this-intersection-doesn-t-have-2-stages","errorCode":null,"errorMessage":"This intersection doesn't have 2 stages.","messagePattern":"This intersection doesn't have 2 stages\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"map_model/src/objects/traffic_signals.rs","lineNumber":215,"sourceCode":"\n        if !has_all_walk {\n            self.stages.push(all_walk_stage);\n        }\n        self != &orig\n    }\n\n    /// Modifies the fixed timing of all stages, applying either a major or minor duration,\n    /// depending on the relative rank of the roads involved in the intersection. If this\n    /// transformation couldn't be applied, returns an error. Even if an error is returned, the\n    /// signal may have been changed -- so only call this on a cloned signal.\n    pub fn adjust_major_minor_timing(\n        &mut self,\n        major: Duration,\n        minor: Duration,\n        map: &Map,\n    ) -> Result<()> {\n        if self.stages.len() != 2 {\n            bail!(\"This intersection doesn't have 2 stages.\");\n        }\n\n        // What's the rank of each road?\n        let mut rank_per_road: BTreeMap<RoadID, usize> = BTreeMap::new();\n        for r in &map.get_i(self.id).roads {\n            rank_per_road.insert(*r, map.get_r(*r).get_detailed_rank());\n        }\n        let mut ranks: Vec<usize> = rank_per_road.values().cloned().collect();\n        ranks.sort_unstable();\n        ranks.dedup();\n        if ranks.len() == 1 {\n            bail!(\"This intersection doesn't have major/minor roads; they're all the same rank.\");\n        }\n        let highest_rank = ranks.pop().unwrap();\n\n        // Try to apply the transformation\n        let orig = self.clone();\n        for stage in &mut self.stages {","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/map_model/src/objects/traffic_signals.rs#L197-L233","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nsignal.adjust_major_minor_timing(major, minor, map)?;\n// after\nif signal.stages.len() == 2 {\n    signal.adjust_major_minor_timing(major, minor, map)?;\n}","handlingStrategy":"validation","validationCode":"if signal.stages.len() != 2 {\n    return Err(anyhow!(\"signal {} has {} stages, expected 2\", signal.id, signal.stages.len()));\n}","typeGuard":"fn is_two_stage(signal: &TrafficSignal) -> bool {\n    signal.stages.len() == 2\n}","tryCatchPattern":"if is_two_stage(signal) {\n    signal.adjust_major_minor_timing(major, minor, map)?;\n} else {\n    skip_and_log(signal.id);\n}","preventionTips":["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"],"tags":["traffic-signals","precondition"],"backgroundTag":"invalid-state-transition","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}