{"record":{"id":"3474dd3433431b16","repo":"a-b-street/abstreet","slug":"this-intersection-didn-t-already-group-major-minor-roads","errorCode":null,"errorMessage":"This intersection didn't already group major/minor roads together.","messagePattern":"This intersection didn't already group major/minor roads together\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"map_model/src/objects/traffic_signals.rs","lineNumber":251,"sourceCode":"        for stage in &mut self.stages {\n            match stage.stage_type {\n                StageType::Fixed(_) => {}\n                _ => bail!(\"This intersection doesn't use fixed timing.\"),\n            }\n            // Ignoring crosswalks, do any of the turns come from a major road?\n            if stage\n                .protected_movements\n                .iter()\n                .any(|m| !m.crosswalk && highest_rank == rank_per_road[&m.from.road])\n            {\n                stage.stage_type = StageType::Fixed(major);\n            } else {\n                stage.stage_type = StageType::Fixed(minor);\n            }\n        }\n\n        if self.simple_cycle_duration() != major + minor {\n            bail!(\"This intersection didn't already group major/minor roads together.\");\n        }\n\n        if self == &orig {\n            bail!(\"This change had no effect.\");\n        }\n\n        Ok(())\n    }\n\n    pub fn missing_turns(&self, i: &Intersection) -> BTreeSet<MovementID> {\n        let mut missing: BTreeSet<MovementID> = i.movements.keys().cloned().collect();\n        for stage in &self.stages {\n            for m in &stage.protected_movements {\n                missing.remove(m);\n            }\n            for m in &stage.yield_movements {\n                missing.remove(m);\n            }","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/map_model/src/objects/traffic_signals.rs#L233-L269","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nsignal.adjust_major_minor_timing(Duration::seconds(30), Duration::seconds(30), map)?;\n// after\nlet total = signal.simple_cycle_duration();\nlet major = total * 2 / 3;\nlet minor = total - major;\nsignal.adjust_major_minor_timing(major, minor, map)?;","handlingStrategy":"validation","validationCode":"let total = signal.simple_cycle_duration();\nif total != major + minor {\n    return Err(anyhow!(\"cycle {} != major {} + minor {}\", total, major, minor));\n}","typeGuard":"fn matches_cycle(signal: &TrafficSignal, major: Duration, minor: Duration) -> bool {\n    signal.simple_cycle_duration() == major + minor\n}","tryCatchPattern":"if matches_cycle(signal, major, minor) {\n    signal.adjust_major_minor_timing(major, minor, map)?;\n} else {\n    let (m, n) = split_current_cycle(signal, map);\n    signal.adjust_major_minor_timing(m, n, map)?;\n}","preventionTips":["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"],"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"}