FAILURE MAP
← Case archive

FA-66876 / Railway interlocking logic / Open access

Four-aspect signal chain evaluation: diverging aspect cap · case 01

A diverging junction signal displays a preliminary caution or green.

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

ROOT CAUSE

The approach-control cap only lowers G, so a double yellow is still shown over the turnout.

THE FAILURE

The approach-control cap only lowers G, so a double yellow is still shown over the turnout.

Unsuccessful approach: Capping only YY lets a green be shown over the diverging route.

Case contract

Signals 0..n-1 each protect one block; occupied[i] is the block after signal i. Evaluate from the far end: a signal shows R if its block is occupied, else Y if the next signal is R, R if the next signal is DARK, YY if the next is Y, otherwise G. Beyond the last signal the next aspect is G on an open line and R at a buffer stop. A diverging signal is capped at Y. A signal whose red lamp has failed shows DARK instead of R.

Why this case matters

Interlocking logic decides whether trains may be given authority; a wrong decision at this point either grants unsafe movements or strands traffic.

1 / The failure

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

N = 1
observations = []
def solve(x):
    occ = x['occupied']
    n = len(occ)
    fail = set(x['lamp_fail'])
    div = set(x['diverging'])
    nxt = 'G' if x['open_end'] else 'R'
    out = [None] * n
    for i in range(n - 1, -1, -1):
        if occ[i]:
            a = 'R'
        elif nxt in ('R', 'DARK'):
            a = 'R' if nxt == 'DARK' else 'Y'
        elif nxt == 'Y':
            a = 'YY'
        else:
            a = 'G'
        if i in div and a == 'G':
            a = 'Y'
        if a == 'R' and i in fail:
            a = 'DARK'
        out[i] = a
        nxt = a
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 24', {'occupied': [False, False, False, True], 'lamp_fail': [1], 'diverging': [1], 'open_end': True}, ['YY', 'Y', 'Y', 'R']), ('control 9', {'occupied': [False, False, False, False, True, False, False], 'lamp_fail': [1], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'YY', 'Y', 'R', 'G', 'G']), ('boundary: dark signal ahead', {'occupied': [False, True], 'lamp_fail': [1], 'diverging': [], 'open_end': True}, ['R', 'DARK']), ('control 1', {'occupied': [False, True, False, False, False, False], 'lamp_fail': [0], 'diverging': [], 'open_end': False}, ['Y', 'R', 'G', 'G', 'YY', 'Y']), ('control 4', {'occupied': [False, True], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['Y', 'R']), ('control 7', {'occupied': [True, False, False, True], 'lamp_fail': [], 'diverging': [3], 'open_end': True}, ['R', 'YY', 'Y', 'R'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 64', {'occupied': [False, False, True, False, False, True], 'lamp_fail': [4], 'diverging': [0], 'open_end': False}, ['Y', 'Y', 'R', 'YY', 'Y', 'R']), ('control 34', {'occupied': [False, False, False, False, False, False, False], 'lamp_fail': [], 'diverging': [1], 'open_end': False}, ['YY', 'Y', 'G', 'G', 'G', 'YY', 'Y']), ('boundary: buffer stop at the end', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['G', 'YY', 'Y']), ('control 12', {'occupied': [False, False], 'lamp_fail': [0], 'diverging': [], 'open_end': False}, ['YY', 'Y']), ('control 15', {'occupied': [False, True], 'lamp_fail': [], 'diverging': [], 'open_end': True}, ['Y', 'R']), ('control 18', {'occupied': [False, False], 'lamp_fail': [], 'diverging': [], 'open_end': True}, ['G', 'G'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 28', {'occupied': [False, False, False, True, False, False, False], 'lamp_fail': [6], 'diverging': [1, 5], 'open_end': True}, ['YY', 'Y', 'Y', 'R', 'YY', 'Y', 'G']), ('control 44', {'occupied': [False, False], 'lamp_fail': [0, 1], 'diverging': [1], 'open_end': True}, ['YY', 'Y']), ('boundary: buffer stop at the end', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['G', 'YY', 'Y']), ('control 23', {'occupied': [True, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['R', 'Y', 'R']), ('control 26', {'occupied': [False, False, False, True], 'lamp_fail': [], 'diverging': [3], 'open_end': False}, ['G', 'YY', 'Y', 'R']), ('control 29', {'occupied': [False, True], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['Y', 'R'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('control 58', {'occupied': [True, False], 'lamp_fail': [0], 'diverging': [0, 1], 'open_end': True}, ['DARK', 'Y']), ('boundary: train two blocks ahead', {'occupied': [False, False, True, False], 'lamp_fail': [], 'diverging': [], 'open_end': True}, ['YY', 'Y', 'R', 'G']), ('boundary: dark signal at the buffer end', {'occupied': [False, False], 'lamp_fail': [1], 'diverging': [], 'open_end': False}, ['YY', 'Y']), ('control 34', {'occupied': [False, False, False, False, False, False, False], 'lamp_fail': [], 'diverging': [1], 'open_end': False}, ['YY', 'Y', 'G', 'G', 'G', 'YY', 'Y']), ('control 37', {'occupied': [False, False, False, False], 'lamp_fail': [0], 'diverging': [0, 3], 'open_end': True}, ['Y', 'G', 'YY', 'Y']), ('control 40', {'occupied': [False, False, False, False], 'lamp_fail': [0], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G', 'G'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 39', {'occupied': [False, False, True, False, False, True], 'lamp_fail': [0, 4], 'diverging': [3], 'open_end': False}, ['YY', 'Y', 'R', 'Y', 'Y', 'R']), ('control 21', {'occupied': [True, False, True, False, False, False], 'lamp_fail': [1, 5], 'diverging': [1, 3], 'open_end': True}, ['R', 'Y', 'R', 'Y', 'G', 'G']), ('boundary: dark signal ahead', {'occupied': [False, True], 'lamp_fail': [1], 'diverging': [], 'open_end': True}, ['R', 'DARK']), ('control 45', {'occupied': [True, False, False, True], 'lamp_fail': [3], 'diverging': [1], 'open_end': True}, ['R', 'Y', 'R', 'DARK']), ('control 48', {'occupied': [False, False, False, False], 'lamp_fail': [1], 'diverging': [2], 'open_end': True}, ['G', 'YY', 'Y', 'G']), ('control 51', {'occupied': [False, False, False, True], 'lamp_fail': [], 'diverging': [0, 2, 3], 'open_end': True}, ['Y', 'YY', 'Y', 'R'])]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
regression: diverging junction behind a double yellow['YY', 'Y', 'R']['Y', 'Y', 'R']Failed
boundary: diverging junction behind a green['Y', 'G', 'G']['Y', 'G', 'G']Passed
sampled regression 24['G', 'YY', 'Y', 'R']['YY', 'Y', 'Y', 'R']Failed
control 9['Y', 'G', 'YY', 'Y', 'R', 'G', 'G']['Y', 'G', 'YY', 'Y', 'R', 'G', 'G']Passed
boundary: dark signal ahead['R', 'DARK']['R', 'DARK']Passed
control 1['Y', 'R', 'G', 'G', 'YY', 'Y']['Y', 'R', 'G', 'G', 'YY', 'Y']Passed
control 4['Y', 'R']['Y', 'R']Passed
control 7['R', 'YY', 'Y', 'R']['R', 'YY', 'Y', 'R']Passed

SHA-256 / 7f316bf6cb48444f8bc837be1b07da3c7c83fead2b876ac3ad3a98b59331c787

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    occ = x['occupied']
    n = len(occ)
    fail = set(x['lamp_fail'])
    div = set(x['diverging'])
    nxt = 'G' if x['open_end'] else 'R'
    out = [None] * n
    for i in range(n - 1, -1, -1):
        if occ[i]:
            a = 'R'
        elif nxt in ('R', 'DARK'):
            a = 'R' if nxt == 'DARK' else 'Y'
        elif nxt == 'Y':
            a = 'YY'
        else:
            a = 'G'
        if i in div and a == 'YY':
            a = 'Y'
        if a == 'R' and i in fail:
            a = 'DARK'
        out[i] = a
        nxt = a
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 24', {'occupied': [False, False, False, True], 'lamp_fail': [1], 'diverging': [1], 'open_end': True}, ['YY', 'Y', 'Y', 'R']), ('control 9', {'occupied': [False, False, False, False, True, False, False], 'lamp_fail': [1], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'YY', 'Y', 'R', 'G', 'G']), ('boundary: dark signal ahead', {'occupied': [False, True], 'lamp_fail': [1], 'diverging': [], 'open_end': True}, ['R', 'DARK']), ('control 1', {'occupied': [False, True, False, False, False, False], 'lamp_fail': [0], 'diverging': [], 'open_end': False}, ['Y', 'R', 'G', 'G', 'YY', 'Y']), ('control 4', {'occupied': [False, True], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['Y', 'R']), ('control 7', {'occupied': [True, False, False, True], 'lamp_fail': [], 'diverging': [3], 'open_end': True}, ['R', 'YY', 'Y', 'R'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 64', {'occupied': [False, False, True, False, False, True], 'lamp_fail': [4], 'diverging': [0], 'open_end': False}, ['Y', 'Y', 'R', 'YY', 'Y', 'R']), ('control 34', {'occupied': [False, False, False, False, False, False, False], 'lamp_fail': [], 'diverging': [1], 'open_end': False}, ['YY', 'Y', 'G', 'G', 'G', 'YY', 'Y']), ('boundary: buffer stop at the end', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['G', 'YY', 'Y']), ('control 12', {'occupied': [False, False], 'lamp_fail': [0], 'diverging': [], 'open_end': False}, ['YY', 'Y']), ('control 15', {'occupied': [False, True], 'lamp_fail': [], 'diverging': [], 'open_end': True}, ['Y', 'R']), ('control 18', {'occupied': [False, False], 'lamp_fail': [], 'diverging': [], 'open_end': True}, ['G', 'G'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 28', {'occupied': [False, False, False, True, False, False, False], 'lamp_fail': [6], 'diverging': [1, 5], 'open_end': True}, ['YY', 'Y', 'Y', 'R', 'YY', 'Y', 'G']), ('control 44', {'occupied': [False, False], 'lamp_fail': [0, 1], 'diverging': [1], 'open_end': True}, ['YY', 'Y']), ('boundary: buffer stop at the end', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['G', 'YY', 'Y']), ('control 23', {'occupied': [True, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['R', 'Y', 'R']), ('control 26', {'occupied': [False, False, False, True], 'lamp_fail': [], 'diverging': [3], 'open_end': False}, ['G', 'YY', 'Y', 'R']), ('control 29', {'occupied': [False, True], 'lamp_fail': [], 'diverging': [], 'open_end': False}, ['Y', 'R'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('control 58', {'occupied': [True, False], 'lamp_fail': [0], 'diverging': [0, 1], 'open_end': True}, ['DARK', 'Y']), ('boundary: train two blocks ahead', {'occupied': [False, False, True, False], 'lamp_fail': [], 'diverging': [], 'open_end': True}, ['YY', 'Y', 'R', 'G']), ('boundary: dark signal at the buffer end', {'occupied': [False, False], 'lamp_fail': [1], 'diverging': [], 'open_end': False}, ['YY', 'Y']), ('control 34', {'occupied': [False, False, False, False, False, False, False], 'lamp_fail': [], 'diverging': [1], 'open_end': False}, ['YY', 'Y', 'G', 'G', 'G', 'YY', 'Y']), ('control 37', {'occupied': [False, False, False, False], 'lamp_fail': [0], 'diverging': [0, 3], 'open_end': True}, ['Y', 'G', 'YY', 'Y']), ('control 40', {'occupied': [False, False, False, False], 'lamp_fail': [0], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G', 'G'])], [('regression: diverging junction behind a double yellow', {'occupied': [False, False, True], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'Y', 'R']), ('boundary: diverging junction behind a green', {'occupied': [False, False, False], 'lamp_fail': [], 'diverging': [0], 'open_end': True}, ['Y', 'G', 'G']), ('sampled regression 39', {'occupied': [False, False, True, False, False, True], 'lamp_fail': [0, 4], 'diverging': [3], 'open_end': False}, ['YY', 'Y', 'R', 'Y', 'Y', 'R']), ('control 21', {'occupied': [True, False, True, False, False, False], 'lamp_fail': [1, 5], 'diverging': [1, 3], 'open_end': True}, ['R', 'Y', 'R', 'Y', 'G', 'G']), ('boundary: dark signal ahead', {'occupied': [False, True], 'lamp_fail': [1], 'diverging': [], 'open_end': True}, ['R', 'DARK']), ('control 45', {'occupied': [True, False, False, True], 'lamp_fail': [3], 'diverging': [1], 'open_end': True}, ['R', 'Y', 'R', 'DARK']), ('control 48', {'occupied': [False, False, False, False], 'lamp_fail': [1], 'diverging': [2], 'open_end': True}, ['G', 'YY', 'Y', 'G']), ('control 51', {'occupied': [False, False, False, True], 'lamp_fail': [], 'diverging': [0, 2, 3], 'open_end': True}, ['Y', 'YY', 'Y', 'R'])]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
regression: diverging junction behind a double yellow['Y', 'Y', 'R']['Y', 'Y', 'R']Passed
boundary: diverging junction behind a green['G', 'G', 'G']['Y', 'G', 'G']Failed
sampled regression 24['YY', 'Y', 'Y', 'R']['YY', 'Y', 'Y', 'R']Passed
control 9['G', 'G', 'YY', 'Y', 'R', 'G', 'G']['Y', 'G', 'YY', 'Y', 'R', 'G', 'G']Failed
boundary: dark signal ahead['R', 'DARK']['R', 'DARK']Passed
control 1['Y', 'R', 'G', 'G', 'YY', 'Y']['Y', 'R', 'G', 'G', 'YY', 'Y']Passed
control 4['Y', 'R']['Y', 'R']Passed
control 7['R', 'YY', 'Y', 'R']['R', 'YY', 'Y', 'R']Passed

SHA-256 / 591a68ed6e22e141d402626ac6830943b629a7dce86dae1c1ffe1ee93ebddb9d

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

Stipulated toy interlocking contract for a bounded teaching model; it makes no claim of conformance to any railway signalling standard and omits real safety cases. 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:47:47.730242+00:00.

Case digest / 726cac81eb7209c2d1f5b38dc13ac0652e484c3b849bba6fc64846ce6f633952