FAILURE MAP
← Case archive

FA-68336 / Traffic signal timing plans / Open access

Overlap phase indication: overlap yellow follows any yellow phase at the intersection · case 01

Overlap phase indication returns a wrong result when overlap yellow follows any yellow phase at the intersection.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Any yellow at the intersection, including non-parent phases, makes the overlap yellow while its parent is already in red clearance.

VERIFIED REPAIR

Restore the overlap yellow choice rule so that the step reads `if any(cur[p] == 'Y' for p in terminating):`.

Unsuccessful approach: Looking only at the lowest terminating parent misses a yellow on a higher parent.

Case contract

Input {parents, not_during, state: [[phase, G|Y|RC|R]] for timing phases, next: phases to be served next}. The overlap is red while any not_during phase is green. Otherwise it is green while any parent is green. If a parent is terminating (Y or RC) the overlap stays green when any next phase is a parent; otherwise it is yellow if any terminating parent is in yellow, else red. Otherwise red.

Why this case matters

Signal timing arithmetic is exact and integer or rational; a wrong rule silently produces unsafe or inefficient timing plans.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    par = set(x['parents'])
    nd = set(x['not_during'])
    cur = {ph: iv for ph, iv in x['state']}
    if any(cur.get(p) == 'G' for p in nd):
        return 'R'
    if any(cur.get(p) == 'G' for p in par):
        return 'G'
    terminating = [p for p in sorted(par) if cur.get(p) in ('Y', 'RC')]
    if terminating:
        if any(p in par for p in x['next']):
            return 'G'
        if any(iv == 'Y' for iv in cur.values()):
            return 'Y'
        return 'R'
    return 'R'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'parents': [1, 6, 8], 'not_during': [], 'state': [[7, 'Y'], [1, 'RC'], [4, 'G']], 'next': []}, 'R'), ({'parents': [5], 'not_during': [], 'state': [[5, 'RC']], 'next': [7]}, 'R'), ({'parents': [1, 2, 3], 'not_during': [], 'state': [[4, 'Y'], [2, 'RC'], [1, 'RC']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 7], 'not_during': [2], 'state': [[7, 'R']], 'next': []}, 'R'), ({'parents': [2, 6], 'not_during': [], 'state': [[1, 'G'], [2, 'RC'], [8, 'Y']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G')], [({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 2, 6], 'not_during': [5, 8], 'state': [[5, 'Y'], [6, 'RC']], 'next': [5]}, 'R'), ({'parents': [3, 4], 'not_during': [], 'state': [[7, 'RC']], 'next': [1, 8]}, 'R'), ({'parents': [3], 'not_during': [5, 7], 'state': [[2, 'G'], [8, 'G'], [3, 'G']], 'next': [4, 7]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [3, 4, 8], 'not_during': [1], 'state': [[7, 'G'], [6, 'Y'], [4, 'RC']], 'next': [6, 7]}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 8], 'not_during': [5, 6], 'state': [[2, 'RC'], [3, 'R'], [8, 'Y']], 'next': []}, 'Y')], [({'parents': [6, 7], 'not_during': [4], 'state': [[7, 'RC'], [4, 'Y'], [3, 'G']], 'next': [5, 8]}, 'R'), ({'parents': [6], 'not_during': [1, 5], 'state': [[6, 'RC'], [3, 'Y']], 'next': [3, 4]}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 8], 'not_during': [2], 'state': [[7, 'R'], [8, 'G']], 'next': []}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 2, 4], 'not_during': [6], 'state': [[6, 'Y'], [2, 'RC']], 'next': [3]}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 7], 'not_during': [8], 'state': [[8, 'R'], [3, 'Y']], 'next': []}, 'R')], [({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2], 'not_during': [], 'state': [[3, 'Y'], [1, 'G'], [2, 'RC']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 2], 'not_during': [4], 'state': [[7, 'Y'], [1, 'RC'], [2, 'Y']], 'next': []}, 'Y'), ({'parents': [1, 5], 'not_during': [3], 'state': [[7, 'R'], [4, 'Y'], [5, 'RC']], 'next': []}, 'R'), ({'parents': [4], 'not_during': [3, 8], 'state': [[4, 'RC'], [3, 'Y'], [5, 'Y']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [1, 6], 'not_during': [5, 7], 'state': [[5, 'G'], [4, 'Y'], [8, 'R']], 'next': []}, 'R')], [({'parents': [3, 5], 'not_during': [], 'state': [[8, 'G'], [2, 'G'], [7, 'RC']], 'next': [2, 7]}, 'R'), ({'parents': [6, 8], 'not_during': [], 'state': [[2, 'Y'], [4, 'Y'], [6, 'RC']], 'next': []}, 'R'), ({'parents': [1, 2, 7], 'not_during': [4, 6], 'state': [[7, 'RC'], [6, 'R'], [8, 'Y']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 4, 6], 'not_during': [2], 'state': [[5, 'R'], [1, 'RC'], [3, 'Y']], 'next': []}, 'R'), ({'parents': [1, 2], 'not_during': [], 'state': [[2, 'RC']], 'next': [7]}, 'R')]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check('timing oracle' + ' %d' % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
timing oracle 0YRFailed
timing oracle 1RRPassed
timing oracle 2YRFailed
timing oracle 3YYPassed
timing oracle 4RRPassed
timing oracle 5RRPassed
timing oracle 6YRFailed
timing oracle 7GGPassed

SHA-256 / c995c03ad54de7c3a36024c48e01b0cd6c81d281447ff11e55fec51b3e238830

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    par = set(x['parents'])
    nd = set(x['not_during'])
    cur = {ph: iv for ph, iv in x['state']}
    if any(cur.get(p) == 'G' for p in nd):
        return 'R'
    if any(cur.get(p) == 'G' for p in par):
        return 'G'
    terminating = [p for p in sorted(par) if cur.get(p) in ('Y', 'RC')]
    if terminating:
        if any(p in par for p in x['next']):
            return 'G'
        if cur[terminating[0]] == 'Y':
            return 'Y'
        return 'R'
    return 'R'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'parents': [1, 6, 8], 'not_during': [], 'state': [[7, 'Y'], [1, 'RC'], [4, 'G']], 'next': []}, 'R'), ({'parents': [5], 'not_during': [], 'state': [[5, 'RC']], 'next': [7]}, 'R'), ({'parents': [1, 2, 3], 'not_during': [], 'state': [[4, 'Y'], [2, 'RC'], [1, 'RC']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 7], 'not_during': [2], 'state': [[7, 'R']], 'next': []}, 'R'), ({'parents': [2, 6], 'not_during': [], 'state': [[1, 'G'], [2, 'RC'], [8, 'Y']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G')], [({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 2, 6], 'not_during': [5, 8], 'state': [[5, 'Y'], [6, 'RC']], 'next': [5]}, 'R'), ({'parents': [3, 4], 'not_during': [], 'state': [[7, 'RC']], 'next': [1, 8]}, 'R'), ({'parents': [3], 'not_during': [5, 7], 'state': [[2, 'G'], [8, 'G'], [3, 'G']], 'next': [4, 7]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [3, 4, 8], 'not_during': [1], 'state': [[7, 'G'], [6, 'Y'], [4, 'RC']], 'next': [6, 7]}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 8], 'not_during': [5, 6], 'state': [[2, 'RC'], [3, 'R'], [8, 'Y']], 'next': []}, 'Y')], [({'parents': [6, 7], 'not_during': [4], 'state': [[7, 'RC'], [4, 'Y'], [3, 'G']], 'next': [5, 8]}, 'R'), ({'parents': [6], 'not_during': [1, 5], 'state': [[6, 'RC'], [3, 'Y']], 'next': [3, 4]}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 8], 'not_during': [2], 'state': [[7, 'R'], [8, 'G']], 'next': []}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 2, 4], 'not_during': [6], 'state': [[6, 'Y'], [2, 'RC']], 'next': [3]}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 7], 'not_during': [8], 'state': [[8, 'R'], [3, 'Y']], 'next': []}, 'R')], [({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2], 'not_during': [], 'state': [[3, 'Y'], [1, 'G'], [2, 'RC']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 2], 'not_during': [4], 'state': [[7, 'Y'], [1, 'RC'], [2, 'Y']], 'next': []}, 'Y'), ({'parents': [1, 5], 'not_during': [3], 'state': [[7, 'R'], [4, 'Y'], [5, 'RC']], 'next': []}, 'R'), ({'parents': [4], 'not_during': [3, 8], 'state': [[4, 'RC'], [3, 'Y'], [5, 'Y']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [1, 6], 'not_during': [5, 7], 'state': [[5, 'G'], [4, 'Y'], [8, 'R']], 'next': []}, 'R')], [({'parents': [3, 5], 'not_during': [], 'state': [[8, 'G'], [2, 'G'], [7, 'RC']], 'next': [2, 7]}, 'R'), ({'parents': [6, 8], 'not_during': [], 'state': [[2, 'Y'], [4, 'Y'], [6, 'RC']], 'next': []}, 'R'), ({'parents': [1, 2, 7], 'not_during': [4, 6], 'state': [[7, 'RC'], [6, 'R'], [8, 'Y']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 4, 6], 'not_during': [2], 'state': [[5, 'R'], [1, 'RC'], [3, 'Y']], 'next': []}, 'R'), ({'parents': [1, 2], 'not_during': [], 'state': [[2, 'RC']], 'next': [7]}, 'R')]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check('timing oracle' + ' %d' % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
timing oracle 0RRPassed
timing oracle 1RRPassed
timing oracle 2RRPassed
timing oracle 3RYFailed
timing oracle 4RRPassed
timing oracle 5RRPassed
timing oracle 6RRPassed
timing oracle 7GGPassed

SHA-256 / 1aa2221e4846736df0b5f9a15906ba61777b07c9e381b50510e8c4863f2de47b

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    par = set(x['parents'])
    nd = set(x['not_during'])
    cur = {ph: iv for ph, iv in x['state']}
    if any(cur.get(p) == 'G' for p in nd):
        return 'R'
    if any(cur.get(p) == 'G' for p in par):
        return 'G'
    terminating = [p for p in sorted(par) if cur.get(p) in ('Y', 'RC')]
    if terminating:
        if any(p in par for p in x['next']):
            return 'G'
        if any(cur[p] == 'Y' for p in terminating):
            return 'Y'
        return 'R'
    return 'R'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'parents': [1, 6, 8], 'not_during': [], 'state': [[7, 'Y'], [1, 'RC'], [4, 'G']], 'next': []}, 'R'), ({'parents': [5], 'not_during': [], 'state': [[5, 'RC']], 'next': [7]}, 'R'), ({'parents': [1, 2, 3], 'not_during': [], 'state': [[4, 'Y'], [2, 'RC'], [1, 'RC']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 7], 'not_during': [2], 'state': [[7, 'R']], 'next': []}, 'R'), ({'parents': [2, 6], 'not_during': [], 'state': [[1, 'G'], [2, 'RC'], [8, 'Y']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G')], [({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 2, 6], 'not_during': [5, 8], 'state': [[5, 'Y'], [6, 'RC']], 'next': [5]}, 'R'), ({'parents': [3, 4], 'not_during': [], 'state': [[7, 'RC']], 'next': [1, 8]}, 'R'), ({'parents': [3], 'not_during': [5, 7], 'state': [[2, 'G'], [8, 'G'], [3, 'G']], 'next': [4, 7]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [3, 4, 8], 'not_during': [1], 'state': [[7, 'G'], [6, 'Y'], [4, 'RC']], 'next': [6, 7]}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 8], 'not_during': [5, 6], 'state': [[2, 'RC'], [3, 'R'], [8, 'Y']], 'next': []}, 'Y')], [({'parents': [6, 7], 'not_during': [4], 'state': [[7, 'RC'], [4, 'Y'], [3, 'G']], 'next': [5, 8]}, 'R'), ({'parents': [6], 'not_during': [1, 5], 'state': [[6, 'RC'], [3, 'Y']], 'next': [3, 4]}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 8], 'not_during': [2], 'state': [[7, 'R'], [8, 'G']], 'next': []}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 2, 4], 'not_during': [6], 'state': [[6, 'Y'], [2, 'RC']], 'next': [3]}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 7], 'not_during': [8], 'state': [[8, 'R'], [3, 'Y']], 'next': []}, 'R')], [({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2], 'not_during': [], 'state': [[3, 'Y'], [1, 'G'], [2, 'RC']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 2], 'not_during': [4], 'state': [[7, 'Y'], [1, 'RC'], [2, 'Y']], 'next': []}, 'Y'), ({'parents': [1, 5], 'not_during': [3], 'state': [[7, 'R'], [4, 'Y'], [5, 'RC']], 'next': []}, 'R'), ({'parents': [4], 'not_during': [3, 8], 'state': [[4, 'RC'], [3, 'Y'], [5, 'Y']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [1, 6], 'not_during': [5, 7], 'state': [[5, 'G'], [4, 'Y'], [8, 'R']], 'next': []}, 'R')], [({'parents': [3, 5], 'not_during': [], 'state': [[8, 'G'], [2, 'G'], [7, 'RC']], 'next': [2, 7]}, 'R'), ({'parents': [6, 8], 'not_during': [], 'state': [[2, 'Y'], [4, 'Y'], [6, 'RC']], 'next': []}, 'R'), ({'parents': [1, 2, 7], 'not_during': [4, 6], 'state': [[7, 'RC'], [6, 'R'], [8, 'Y']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [1, 4, 6], 'not_during': [2], 'state': [[5, 'R'], [1, 'RC'], [3, 'Y']], 'next': []}, 'R'), ({'parents': [1, 2], 'not_during': [], 'state': [[2, 'RC']], 'next': [7]}, 'R')]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check('timing oracle' + ' %d' % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
timing oracle 0RRPassed
timing oracle 1RRPassed
timing oracle 2RRPassed
timing oracle 3YYPassed
timing oracle 4RRPassed
timing oracle 5RRPassed
timing oracle 6RRPassed
timing oracle 7GGPassed

SHA-256 / 8b57fcc0c00d0c9b3f25213cc64569dc3e9d715f1dec8d03b89de7b6e77df935

Verification & scope

A deterministic, bounded toy model with a stipulated contract; it makes no claim of conformance to any agency manual or standard. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:48:01.256633+00:00.

Case digest / b65a86b6661f6a295989977128755bb442f5be3488d294c655bcb7b5e1e55ec4