FA-68326 / Traffic signal timing plans / Open access
Overlap phase indication: the overlap continues only when the same parent is re-served · case 01
Overlap phase indication returns a wrong result when the overlap continues only when the same parent is re-served.
ROOT CAUSE
The continuation test looks for the terminating parent in the next phases instead of any parent, so a transfer from one parent to another drops the overlap.
VERIFIED REPAIR
Restore the parent-to-parent transition rule so that the step reads `if any(p in par for p in x['next']):`.
Unsuccessful approach: Inspecting only the first next phase misses a parent listed second.
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 terminating 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': [3, 4, 7], 'not_during': [5, 6], 'state': [[6, 'R'], [3, 'R'], [7, 'Y']], 'next': [1, 6]}, 'Y'), ({'parents': [4, 5, 8], 'not_during': [], 'state': [[4, 'Y'], [6, 'R']], 'next': [7, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4], 'not_during': [2], 'state': [[8, 'Y'], [4, 'RC']], 'next': [3, 4]}, 'G'), ({'parents': [2, 6, 7], 'not_during': [], 'state': [[7, 'G'], [1, 'RC'], [3, 'G']], 'next': []}, 'G'), ({'parents': [3, 4, 7], 'not_during': [6], 'state': [[4, 'Y'], [7, 'R']], 'next': [1, 4]}, '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': [6, 8], 'not_during': [], 'state': [[6, 'Y'], [1, 'RC'], [2, 'Y']], 'next': [4, 6]}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 6, 8], 'not_during': [1], 'state': [[6, 'Y']], 'next': [3, 6]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 2, 5], 'not_during': [7], 'state': [[3, 'Y'], [1, 'RC']], 'next': [2, 8]}, 'G'), ({'parents': [4], 'not_during': [1, 8], 'state': [[4, 'Y'], [1, 'RC']], 'next': []}, 'Y'), ({'parents': [5, 6, 8], 'not_during': [], 'state': [[5, 'Y'], [3, 'G'], [4, 'Y']], 'next': [8]}, 'G')], [({'parents': [2, 4, 8], 'not_during': [1], 'state': [[1, 'Y'], [8, 'R'], [4, 'Y']], 'next': [3, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [2, 5], 'not_during': [], 'state': [[7, 'Y'], [5, 'R'], [1, 'R']], 'next': [6]}, 'R'), ({'parents': [3], 'not_during': [], 'state': [[1, 'G']], 'next': []}, 'R'), ({'parents': [5, 7], 'not_during': [3], 'state': [[6, 'Y'], [7, 'Y'], [2, 'G']], 'next': [5]}, 'G'), ({'parents': [5], 'not_during': [7], 'state': [[3, 'G'], [7, 'G'], [8, 'R']], 'next': [8]}, 'R'), ({'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': [3, 8], 'not_during': [2, 7], 'state': [[8, 'RC']], 'next': [8]}, 'G'), ({'parents': [3], 'not_during': [6], 'state': [[7, 'G'], [5, 'RC'], [6, 'R']], 'next': []}, 'R'), ({'parents': [1, 7], 'not_during': [2], 'state': [[7, 'Y'], [2, 'R']], 'next': [4, 7]}, 'G'), ({'parents': [3, 6, 7], 'not_during': [], 'state': [[2, 'Y'], [3, 'Y'], [1, 'G']], 'next': [2, 6]}, 'G'), ({'parents': [2, 6, 7], 'not_during': [], 'state': [[7, 'Y'], [5, 'R'], [4, 'Y']], 'next': [2, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'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, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [6, 7], 'not_during': [2, 4], 'state': [[7, 'Y'], [2, 'G'], [1, 'R']], 'next': [3]}, 'R'), ({'parents': [5, 7], 'not_during': [], 'state': [[6, 'R'], [8, 'Y']], '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': [6, 8], 'not_during': [3], 'state': [[8, 'RC']], 'next': [3, 6]}, 'G'), ({'parents': [1, 4, 8], 'not_during': [3], 'state': [[1, 'Y']], 'next': [3, 8]}, 'G'), ({'parents': [4, 5, 7], 'not_during': [], 'state': [[3, 'G'], [4, 'Y'], [2, 'R']], 'next': [5, 7]}, 'G')]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| timing oracle 0 | Y | Y | Passed |
| timing oracle 1 | Y | G | Failed |
| timing oracle 2 | R | R | Passed |
| timing oracle 3 | G | G | Passed |
| timing oracle 4 | G | G | Passed |
| timing oracle 5 | G | G | Passed |
| timing oracle 6 | R | G | Failed |
| timing oracle 7 | Y | Y | Passed |
SHA-256 / 22e53dde63fe835631623740c3b5536084ea12033485c284493ca5c42813ad93
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 x['next'] and x['next'][0] in par:
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': [3, 4, 7], 'not_during': [5, 6], 'state': [[6, 'R'], [3, 'R'], [7, 'Y']], 'next': [1, 6]}, 'Y'), ({'parents': [4, 5, 8], 'not_during': [], 'state': [[4, 'Y'], [6, 'R']], 'next': [7, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4], 'not_during': [2], 'state': [[8, 'Y'], [4, 'RC']], 'next': [3, 4]}, 'G'), ({'parents': [2, 6, 7], 'not_during': [], 'state': [[7, 'G'], [1, 'RC'], [3, 'G']], 'next': []}, 'G'), ({'parents': [3, 4, 7], 'not_during': [6], 'state': [[4, 'Y'], [7, 'R']], 'next': [1, 4]}, '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': [6, 8], 'not_during': [], 'state': [[6, 'Y'], [1, 'RC'], [2, 'Y']], 'next': [4, 6]}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 6, 8], 'not_during': [1], 'state': [[6, 'Y']], 'next': [3, 6]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 2, 5], 'not_during': [7], 'state': [[3, 'Y'], [1, 'RC']], 'next': [2, 8]}, 'G'), ({'parents': [4], 'not_during': [1, 8], 'state': [[4, 'Y'], [1, 'RC']], 'next': []}, 'Y'), ({'parents': [5, 6, 8], 'not_during': [], 'state': [[5, 'Y'], [3, 'G'], [4, 'Y']], 'next': [8]}, 'G')], [({'parents': [2, 4, 8], 'not_during': [1], 'state': [[1, 'Y'], [8, 'R'], [4, 'Y']], 'next': [3, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [2, 5], 'not_during': [], 'state': [[7, 'Y'], [5, 'R'], [1, 'R']], 'next': [6]}, 'R'), ({'parents': [3], 'not_during': [], 'state': [[1, 'G']], 'next': []}, 'R'), ({'parents': [5, 7], 'not_during': [3], 'state': [[6, 'Y'], [7, 'Y'], [2, 'G']], 'next': [5]}, 'G'), ({'parents': [5], 'not_during': [7], 'state': [[3, 'G'], [7, 'G'], [8, 'R']], 'next': [8]}, 'R'), ({'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': [3, 8], 'not_during': [2, 7], 'state': [[8, 'RC']], 'next': [8]}, 'G'), ({'parents': [3], 'not_during': [6], 'state': [[7, 'G'], [5, 'RC'], [6, 'R']], 'next': []}, 'R'), ({'parents': [1, 7], 'not_during': [2], 'state': [[7, 'Y'], [2, 'R']], 'next': [4, 7]}, 'G'), ({'parents': [3, 6, 7], 'not_during': [], 'state': [[2, 'Y'], [3, 'Y'], [1, 'G']], 'next': [2, 6]}, 'G'), ({'parents': [2, 6, 7], 'not_during': [], 'state': [[7, 'Y'], [5, 'R'], [4, 'Y']], 'next': [2, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'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, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [6, 7], 'not_during': [2, 4], 'state': [[7, 'Y'], [2, 'G'], [1, 'R']], 'next': [3]}, 'R'), ({'parents': [5, 7], 'not_during': [], 'state': [[6, 'R'], [8, 'Y']], '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': [6, 8], 'not_during': [3], 'state': [[8, 'RC']], 'next': [3, 6]}, 'G'), ({'parents': [1, 4, 8], 'not_during': [3], 'state': [[1, 'Y']], 'next': [3, 8]}, 'G'), ({'parents': [4, 5, 7], 'not_during': [], 'state': [[3, 'G'], [4, 'Y'], [2, 'R']], 'next': [5, 7]}, 'G')]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| timing oracle 0 | Y | Y | Passed |
| timing oracle 1 | Y | G | Failed |
| timing oracle 2 | R | R | Passed |
| timing oracle 3 | R | G | Failed |
| timing oracle 4 | G | G | Passed |
| timing oracle 5 | Y | G | Failed |
| timing oracle 6 | G | G | Passed |
| timing oracle 7 | Y | Y | Passed |
SHA-256 / 13f2acf1c1fbdd79b0f05a0e881474c5788a085f4de8bbcda5a9827450f93b03
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': [3, 4, 7], 'not_during': [5, 6], 'state': [[6, 'R'], [3, 'R'], [7, 'Y']], 'next': [1, 6]}, 'Y'), ({'parents': [4, 5, 8], 'not_during': [], 'state': [[4, 'Y'], [6, 'R']], 'next': [7, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [4], 'not_during': [2], 'state': [[8, 'Y'], [4, 'RC']], 'next': [3, 4]}, 'G'), ({'parents': [2, 6, 7], 'not_during': [], 'state': [[7, 'G'], [1, 'RC'], [3, 'G']], 'next': []}, 'G'), ({'parents': [3, 4, 7], 'not_during': [6], 'state': [[4, 'Y'], [7, 'R']], 'next': [1, 4]}, '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': [6, 8], 'not_during': [], 'state': [[6, 'Y'], [1, 'RC'], [2, 'Y']], 'next': [4, 6]}, 'G'), ({'parents': [4, 5], 'not_during': [], 'state': [[4, 'RC'], [5, 'Y']], 'next': [1]}, 'Y'), ({'parents': [2, 6, 8], 'not_during': [1], 'state': [[6, 'Y']], 'next': [3, 6]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [2, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [1, 2, 5], 'not_during': [7], 'state': [[3, 'Y'], [1, 'RC']], 'next': [2, 8]}, 'G'), ({'parents': [4], 'not_during': [1, 8], 'state': [[4, 'Y'], [1, 'RC']], 'next': []}, 'Y'), ({'parents': [5, 6, 8], 'not_during': [], 'state': [[5, 'Y'], [3, 'G'], [4, 'Y']], 'next': [8]}, 'G')], [({'parents': [2, 4, 8], 'not_during': [1], 'state': [[1, 'Y'], [8, 'R'], [4, 'Y']], 'next': [3, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'parents': [2, 5], 'not_during': [], 'state': [[7, 'Y'], [5, 'R'], [1, 'R']], 'next': [6]}, 'R'), ({'parents': [3], 'not_during': [], 'state': [[1, 'G']], 'next': []}, 'R'), ({'parents': [5, 7], 'not_during': [3], 'state': [[6, 'Y'], [7, 'Y'], [2, 'G']], 'next': [5]}, 'G'), ({'parents': [5], 'not_during': [7], 'state': [[3, 'G'], [7, 'G'], [8, 'R']], 'next': [8]}, 'R'), ({'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': [3, 8], 'not_during': [2, 7], 'state': [[8, 'RC']], 'next': [8]}, 'G'), ({'parents': [3], 'not_during': [6], 'state': [[7, 'G'], [5, 'RC'], [6, 'R']], 'next': []}, 'R'), ({'parents': [1, 7], 'not_during': [2], 'state': [[7, 'Y'], [2, 'R']], 'next': [4, 7]}, 'G'), ({'parents': [3, 6, 7], 'not_during': [], 'state': [[2, 'Y'], [3, 'Y'], [1, 'G']], 'next': [2, 6]}, 'G'), ({'parents': [2, 6, 7], 'not_during': [], 'state': [[7, 'Y'], [5, 'R'], [4, 'Y']], 'next': [2, 8]}, 'G'), ({'parents': [2, 3], 'not_during': [5], 'state': [[3, 'G'], [5, 'G']], 'next': []}, 'R'), ({'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, 3], 'not_during': [], 'state': [[2, 'RC'], [6, 'RC']], 'next': [3, 7]}, 'G'), ({'parents': [6, 7], 'not_during': [2, 4], 'state': [[7, 'Y'], [2, 'G'], [1, 'R']], 'next': [3]}, 'R'), ({'parents': [5, 7], 'not_during': [], 'state': [[6, 'R'], [8, 'Y']], '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': [6, 8], 'not_during': [3], 'state': [[8, 'RC']], 'next': [3, 6]}, 'G'), ({'parents': [1, 4, 8], 'not_during': [3], 'state': [[1, 'Y']], 'next': [3, 8]}, 'G'), ({'parents': [4, 5, 7], 'not_during': [], 'state': [[3, 'G'], [4, 'Y'], [2, 'R']], 'next': [5, 7]}, 'G')]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| timing oracle 0 | Y | Y | Passed |
| timing oracle 1 | G | G | Passed |
| timing oracle 2 | R | R | Passed |
| timing oracle 3 | G | G | Passed |
| timing oracle 4 | G | G | Passed |
| timing oracle 5 | G | G | Passed |
| timing oracle 6 | G | G | Passed |
| timing oracle 7 | Y | Y | Passed |
SHA-256 / bf226aae697d486c5c33b7c924bf5d5bfdc41172b67fdcc2bfdbadf9d79ae13e
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.153750+00:00.
Case digest / d6cf245e482d8a20e85224820bbdae67bd738056d8b12bcb79e91194a3401ca6