FAILURE MAP
← Case archive

FA-68321 / Traffic signal timing plans / Open access

Overlap phase indication: a parent in yellow keeps the overlap green · case 01

Overlap phase indication returns a wrong result when a parent in yellow keeps the overlap green.

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

ROOT CAUSE

Yellow is treated as green for parents, so the overlap skips its own yellow and goes straight to red after the parent clears.

VERIFIED REPAIR

Restore the parent green test rule so that the step reads `if any(cur.get(p) == 'G' for p in par):`.

Unsuccessful approach: Checking only the lowest parent ignores the other parents.

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) in ('G', 'Y') 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': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 3, 4], 'not_during': [], 'state': [[8, 'Y'], [7, 'G'], [3, 'Y']], 'next': []}, 'Y'), ({'parents': [1, 2, 3], 'not_during': [], 'state': [[3, 'G']], 'next': [6, 7]}, 'G'), ({'parents': [1, 7], 'not_during': [4, 6], 'state': [[2, 'G'], [1, 'Y'], [8, 'R']], 'next': [8]}, 'Y'), ({'parents': [2, 5, 7], 'not_during': [3, 8], 'state': [[8, 'R'], [5, 'G']], 'next': [4]}, 'G'), ({'parents': [5, 7], 'not_during': [8], 'state': [[4, 'RC']], 'next': [2]}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y')], [({'parents': [3, 4, 7], 'not_during': [], 'state': [[3, 'Y']], 'next': [2, 8]}, 'Y'), ({'parents': [1, 2, 8], 'not_during': [6], 'state': [[8, 'G'], [4, 'Y']], 'next': [3]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [8], 'not_during': [], 'state': [[7, 'Y'], [3, 'G'], [1, 'R']], 'next': [4]}, 'R'), ({'parents': [4, 5, 7], 'not_during': [2], 'state': [[5, 'Y'], [2, 'RC'], [1, 'Y']], 'next': []}, 'Y'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [1, 2, 4], 'not_during': [3, 6], 'state': [[5, 'RC'], [2, 'G'], [1, 'RC']], 'next': [5]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G')], [({'parents': [2, 6, 8], 'not_during': [], 'state': [[7, 'G'], [8, 'G'], [1, 'Y']], 'next': [3, 5]}, 'G'), ({'parents': [3, 4, 5], 'not_during': [], 'state': [[4, 'G']], 'next': [6]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2], 'not_during': [7], 'state': [[2, 'Y'], [6, 'R']], 'next': [3, 7]}, 'Y'), ({'parents': [2], 'not_during': [3, 6], 'state': [[7, 'G'], [2, 'G'], [6, 'Y']], 'next': [1, 4]}, 'G'), ({'parents': [5], 'not_during': [2, 4], 'state': [[1, 'R'], [5, 'Y'], [8, 'R']], 'next': []}, 'Y'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R')], [({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [5, 6, 8], 'not_during': [4], 'state': [[6, 'G']], 'next': [5, 8]}, 'G'), ({'parents': [3, 4, 6], 'not_during': [], 'state': [[7, 'Y'], [6, 'Y']], 'next': [2]}, 'Y'), ({'parents': [6], 'not_during': [], 'state': [[6, 'R'], [5, 'RC']], 'next': []}, 'R'), ({'parents': [4, 5, 8], 'not_during': [2, 3], 'state': [[6, 'G'], [5, 'G'], [8, 'Y']], 'next': [3]}, 'G'), ({'parents': [3, 4, 6], 'not_during': [], 'state': [[1, 'Y'], [4, 'RC'], [3, 'Y']], 'next': []}, '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, 3, 8], 'not_during': [], 'state': [[2, 'G'], [4, 'Y'], [6, 'R']], 'next': [1]}, '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': [1, 7], 'not_during': [2, 5], 'state': [[7, 'G'], [3, 'Y']], 'next': []}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[5, 'Y'], [6, 'G']], 'next': []}, 'Y'), ({'parents': [3, 4], 'not_during': [], 'state': [[4, 'G'], [7, 'G'], [5, 'RC']], 'next': [2]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2, 6], 'not_during': [4], 'state': [[3, 'G'], [2, 'Y'], [4, 'R']], 'next': []}, 'Y')]]
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 0GGPassed
timing oracle 1GYFailed
timing oracle 2GGPassed
timing oracle 3GYFailed
timing oracle 4GGPassed
timing oracle 5RRPassed
timing oracle 6RRPassed
timing oracle 7GYFailed

SHA-256 / 287cefcc66d5def1605ad9ad4ac885f826fd6948c63d02818b1d0cf7275b0515

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 cur.get(min(par)) == 'G':
        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': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 3, 4], 'not_during': [], 'state': [[8, 'Y'], [7, 'G'], [3, 'Y']], 'next': []}, 'Y'), ({'parents': [1, 2, 3], 'not_during': [], 'state': [[3, 'G']], 'next': [6, 7]}, 'G'), ({'parents': [1, 7], 'not_during': [4, 6], 'state': [[2, 'G'], [1, 'Y'], [8, 'R']], 'next': [8]}, 'Y'), ({'parents': [2, 5, 7], 'not_during': [3, 8], 'state': [[8, 'R'], [5, 'G']], 'next': [4]}, 'G'), ({'parents': [5, 7], 'not_during': [8], 'state': [[4, 'RC']], 'next': [2]}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y')], [({'parents': [3, 4, 7], 'not_during': [], 'state': [[3, 'Y']], 'next': [2, 8]}, 'Y'), ({'parents': [1, 2, 8], 'not_during': [6], 'state': [[8, 'G'], [4, 'Y']], 'next': [3]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [8], 'not_during': [], 'state': [[7, 'Y'], [3, 'G'], [1, 'R']], 'next': [4]}, 'R'), ({'parents': [4, 5, 7], 'not_during': [2], 'state': [[5, 'Y'], [2, 'RC'], [1, 'Y']], 'next': []}, 'Y'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [1, 2, 4], 'not_during': [3, 6], 'state': [[5, 'RC'], [2, 'G'], [1, 'RC']], 'next': [5]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G')], [({'parents': [2, 6, 8], 'not_during': [], 'state': [[7, 'G'], [8, 'G'], [1, 'Y']], 'next': [3, 5]}, 'G'), ({'parents': [3, 4, 5], 'not_during': [], 'state': [[4, 'G']], 'next': [6]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2], 'not_during': [7], 'state': [[2, 'Y'], [6, 'R']], 'next': [3, 7]}, 'Y'), ({'parents': [2], 'not_during': [3, 6], 'state': [[7, 'G'], [2, 'G'], [6, 'Y']], 'next': [1, 4]}, 'G'), ({'parents': [5], 'not_during': [2, 4], 'state': [[1, 'R'], [5, 'Y'], [8, 'R']], 'next': []}, 'Y'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R')], [({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [5, 6, 8], 'not_during': [4], 'state': [[6, 'G']], 'next': [5, 8]}, 'G'), ({'parents': [3, 4, 6], 'not_during': [], 'state': [[7, 'Y'], [6, 'Y']], 'next': [2]}, 'Y'), ({'parents': [6], 'not_during': [], 'state': [[6, 'R'], [5, 'RC']], 'next': []}, 'R'), ({'parents': [4, 5, 8], 'not_during': [2, 3], 'state': [[6, 'G'], [5, 'G'], [8, 'Y']], 'next': [3]}, 'G'), ({'parents': [3, 4, 6], 'not_during': [], 'state': [[1, 'Y'], [4, 'RC'], [3, 'Y']], 'next': []}, '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, 3, 8], 'not_during': [], 'state': [[2, 'G'], [4, 'Y'], [6, 'R']], 'next': [1]}, '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': [1, 7], 'not_during': [2, 5], 'state': [[7, 'G'], [3, 'Y']], 'next': []}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[5, 'Y'], [6, 'G']], 'next': []}, 'Y'), ({'parents': [3, 4], 'not_during': [], 'state': [[4, 'G'], [7, 'G'], [5, 'RC']], 'next': [2]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2, 6], 'not_during': [4], 'state': [[3, 'G'], [2, 'Y'], [4, 'R']], 'next': []}, 'Y')]]
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 0GGPassed
timing oracle 1YYPassed
timing oracle 2RGFailed
timing oracle 3YYPassed
timing oracle 4RGFailed
timing oracle 5RRPassed
timing oracle 6RRPassed
timing oracle 7YYPassed

SHA-256 / d01cd98d8a0b023f21f32e493574f205d869a8ebfdff1bdaa59feac178a83c5a

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': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 3, 4], 'not_during': [], 'state': [[8, 'Y'], [7, 'G'], [3, 'Y']], 'next': []}, 'Y'), ({'parents': [1, 2, 3], 'not_during': [], 'state': [[3, 'G']], 'next': [6, 7]}, 'G'), ({'parents': [1, 7], 'not_during': [4, 6], 'state': [[2, 'G'], [1, 'Y'], [8, 'R']], 'next': [8]}, 'Y'), ({'parents': [2, 5, 7], 'not_during': [3, 8], 'state': [[8, 'R'], [5, 'G']], 'next': [4]}, 'G'), ({'parents': [5, 7], 'not_during': [8], 'state': [[4, 'RC']], 'next': [2]}, 'R'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y')], [({'parents': [3, 4, 7], 'not_during': [], 'state': [[3, 'Y']], 'next': [2, 8]}, 'Y'), ({'parents': [1, 2, 8], 'not_during': [6], 'state': [[8, 'G'], [4, 'Y']], 'next': [3]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [8], 'not_during': [], 'state': [[7, 'Y'], [3, 'G'], [1, 'R']], 'next': [4]}, 'R'), ({'parents': [4, 5, 7], 'not_during': [2], 'state': [[5, 'Y'], [2, 'RC'], [1, 'Y']], 'next': []}, 'Y'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [1, 2, 4], 'not_during': [3, 6], 'state': [[5, 'RC'], [2, 'G'], [1, 'RC']], 'next': [5]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G')], [({'parents': [2, 6, 8], 'not_during': [], 'state': [[7, 'G'], [8, 'G'], [1, 'Y']], 'next': [3, 5]}, 'G'), ({'parents': [3, 4, 5], 'not_during': [], 'state': [[4, 'G']], 'next': [6]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2], 'not_during': [7], 'state': [[2, 'Y'], [6, 'R']], 'next': [3, 7]}, 'Y'), ({'parents': [2], 'not_during': [3, 6], 'state': [[7, 'G'], [2, 'G'], [6, 'Y']], 'next': [1, 4]}, 'G'), ({'parents': [5], 'not_during': [2, 4], 'state': [[1, 'R'], [5, 'Y'], [8, 'R']], 'next': []}, 'Y'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R')], [({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [5, 6, 8], 'not_during': [4], 'state': [[6, 'G']], 'next': [5, 8]}, 'G'), ({'parents': [3, 4, 6], 'not_during': [], 'state': [[7, 'Y'], [6, 'Y']], 'next': [2]}, 'Y'), ({'parents': [6], 'not_during': [], 'state': [[6, 'R'], [5, 'RC']], 'next': []}, 'R'), ({'parents': [4, 5, 8], 'not_during': [2, 3], 'state': [[6, 'G'], [5, 'G'], [8, 'Y']], 'next': [3]}, 'G'), ({'parents': [3, 4, 6], 'not_during': [], 'state': [[1, 'Y'], [4, 'RC'], [3, 'Y']], 'next': []}, '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, 3, 8], 'not_during': [], 'state': [[2, 'G'], [4, 'Y'], [6, 'R']], 'next': [1]}, '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': [1, 7], 'not_during': [2, 5], 'state': [[7, 'G'], [3, 'Y']], 'next': []}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[5, 'Y'], [6, 'G']], 'next': []}, 'Y'), ({'parents': [3, 4], 'not_during': [], 'state': [[4, 'G'], [7, 'G'], [5, 'RC']], 'next': [2]}, 'G'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [2, 6], 'not_during': [4], 'state': [[3, 'G'], [2, 'Y'], [4, 'R']], 'next': []}, 'Y')]]
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 0GGPassed
timing oracle 1YYPassed
timing oracle 2GGPassed
timing oracle 3YYPassed
timing oracle 4GGPassed
timing oracle 5RRPassed
timing oracle 6RRPassed
timing oracle 7YYPassed

SHA-256 / 721b4084a10a787b0a6836193eeb19b838a958296c8b89f2293722939049caaf

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.153159+00:00.

Case digest / a80a544574e6cc15d40dd1aea9e5d7b0976c2be268085882fbb14fd5b1482eb3