FAILURE MAP
← Case archive

FA-67026 / Railway interlocking logic / Open access

Overlap timed release: timer start latch · case 01

A bobbing berth track circuit keeps pushing the overlap release back.

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

ROOT CAUSE

Every berth occupation restarts the timer instead of only the first.

THE FAILURE

Every berth occupation restarts the timer instead of only the first.

Unsuccessful approach: Using start or t restarts the timer whenever the train stopped at time zero.

Case contract

The overlap beyond a signal is locked. The release timer starts at the first berth occupation; the overlap is released when an event time reaches start + timer_s while the train has not overrun into the overlap. A berth clear before release cancels the timer (a later occupation restarts it); after release it has no effect. Entering the overlap before release marks an overrun and blocks release. query appends locked or released.

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):
    T = x['timer_s']
    start = None
    overran = False
    released = False
    out = []
    for t, ev in x['events']:
        if start is not None and not released and not overran and t - start >= T:
            released = True
        if ev == 'berth_occ':
            start = t
        elif ev == 'berth_clr':
            if not released:
                start = None
        elif ev == 'overlap_occ':
            if not released:
                overran = True
        elif ev == 'query':
            out.append('released' if released else 'locked')
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 80', {'timer_s': 120, 'events': [[5, 'berth_occ'], [35, 'query'], [65, 'berth_occ'], [155, 'berth_occ'], [155, 'berth_occ'], [215, 'query']]}, ['locked', 'released']), ('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('control 1', {'timer_s': 45, 'events': [[5, 'overlap_occ'], [35, 'query'], [65, 'berth_clr'], [125, 'query'], [170, 'query'], [170, 'berth_clr'], [230, 'berth_occ'], [350, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 4', {'timer_s': 90, 'events': [[60, 'berth_clr'], [150, 'berth_occ'], [155, 'berth_occ'], [155, 'query']]}, ['locked']), ('control 7', {'timer_s': 45, 'events': [[5, 'query'], [65, 'overlap_occ'], [110, 'overlap_occ'], [170, 'query']]}, ['locked', 'locked'])], [('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 46', {'timer_s': 120, 'events': [[90, 'berth_clr'], [120, 'berth_clr'], [120, 'berth_occ'], [150, 'query'], [180, 'berth_occ'], [270, 'berth_occ'], [315, 'overlap_occ'], [375, 'query']]}, ['locked', 'released']), ('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('control 12', {'timer_s': 120, 'events': [[60, 'berth_occ'], [65, 'query'], [70, 'query'], [70, 'query'], [75, 'berth_occ'], [80, 'berth_occ'], [125, 'berth_clr'], [125, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 15', {'timer_s': 120, 'events': [[60, 'berth_clr'], [105, 'berth_clr'], [110, 'berth_clr'], [115, 'berth_clr'], [175, 'query'], [175, 'berth_clr'], [235, 'query'], [265, 'overlap_occ'], [270, 'berth_clr'], [390, 'query']]}, ['locked', 'locked', 'locked']), ('control 18', {'timer_s': 90, 'events': [[0, 'query'], [0, 'query'], [30, 'berth_occ'], [120, 'overlap_occ'], [210, 'berth_clr'], [215, 'query'], [245, 'berth_occ'], [365, 'query']]}, ['locked', 'locked', 'released', 'released'])], [('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('boundary: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('boundary: ninety second timer at one minute', {'timer_s': 90, 'events': [[0, 'berth_occ'], [60, 'query'], [89, 'query'], [90, 'query']]}, ['locked', 'locked', 'released']), ('control 23', {'timer_s': 45, 'events': [[0, 'query'], [5, 'berth_occ'], [95, 'berth_occ'], [185, 'berth_occ'], [245, 'query']]}, ['locked', 'released']), ('control 26', {'timer_s': 60, 'events': [[30, 'query'], [60, 'query'], [150, 'overlap_occ'], [150, 'query']]}, ['locked', 'locked', 'locked']), ('control 29', {'timer_s': 90, 'events': [[90, 'overlap_occ'], [95, 'query'], [95, 'berth_occ'], [185, 'query'], [230, 'query'], [260, 'query'], [350, 'berth_occ'], [410, 'query']]}, ['locked', 'locked', 'locked', 'locked', 'locked'])], [('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: ninety second timer at one minute', {'timer_s': 90, 'events': [[0, 'berth_occ'], [60, 'query'], [89, 'query'], [90, 'query']]}, ['locked', 'locked', 'released']), ('boundary: expiry after a minute boundary', {'timer_s': 120, 'events': [[30, 'berth_occ'], [140, 'query'], [150, 'query']]}, ['locked', 'released']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('control 34', {'timer_s': 60, 'events': [[5, 'query'], [35, 'query'], [80, 'berth_occ'], [170, 'berth_occ'], [260, 'berth_occ'], [320, 'berth_occ'], [350, 'overlap_occ'], [395, 'overlap_occ'], [440, 'query'], [500, 'query'], [560, 'query']]}, ['locked', 'locked', 'released', 'released', 'released']), ('control 37', {'timer_s': 120, 'events': [[30, 'query'], [60, 'overlap_occ'], [120, 'berth_occ'], [180, 'overlap_occ'], [185, 'overlap_occ'], [185, 'overlap_occ'], [245, 'query'], [275, 'query'], [280, 'overlap_occ'], [280, 'berth_occ'], [280, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 40', {'timer_s': 90, 'events': [[60, 'overlap_occ'], [90, 'query'], [135, 'berth_occ'], [195, 'overlap_occ'], [225, 'berth_occ'], [230, 'berth_clr'], [230, 'query']]}, ['locked', 'locked'])], [('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 80', {'timer_s': 120, 'events': [[5, 'berth_occ'], [35, 'query'], [65, 'berth_occ'], [155, 'berth_occ'], [155, 'berth_occ'], [215, 'query']]}, ['locked', 'released']), ('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('control 45', {'timer_s': 60, 'events': [[60, 'query'], [120, 'query'], [150, 'overlap_occ'], [155, 'berth_occ'], [245, 'overlap_occ'], [290, 'berth_occ'], [380, 'berth_clr'], [470, 'overlap_occ'], [470, 'query'], [475, 'berth_occ'], [595, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 48', {'timer_s': 45, 'events': [[60, 'overlap_occ'], [150, 'berth_occ'], [180, 'overlap_occ'], [180, 'berth_occ'], [270, 'berth_clr'], [275, 'berth_clr'], [275, 'overlap_occ'], [275, 'query']]}, ['locked']), ('control 51', {'timer_s': 90, 'events': [[90, 'berth_occ'], [150, 'berth_clr'], [195, 'query'], [255, 'berth_occ'], [315, 'query'], [320, 'query'], [320, 'overlap_occ'], [440, 'query']]}, ['locked', 'locked', 'locked', 'locked'])]]
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: track circuit bob restarts nothing['locked']['released']Failed
sampled regression 80['locked', 'locked']['locked', 'released']Failed
regression: train stops at time zero['locked']['released']Failed
boundary: expiry exactly on time['released']['released']Passed
boundary: train leaves before expiry['locked']['locked']Passed
control 1['locked', 'locked', 'locked', 'locked']['locked', 'locked', 'locked', 'locked']Passed
control 4['locked']['locked']Passed
control 7['locked', 'locked']['locked', 'locked']Passed

SHA-256 / 9a9a7a267109c5384fdbd8681c33beb7a4a5c82237ea5f7968db659e36b24af2

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    T = x['timer_s']
    start = None
    overran = False
    released = False
    out = []
    for t, ev in x['events']:
        if start is not None and not released and not overran and t - start >= T:
            released = True
        if ev == 'berth_occ':
            start = start or t
        elif ev == 'berth_clr':
            if not released:
                start = None
        elif ev == 'overlap_occ':
            if not released:
                overran = True
        elif ev == 'query':
            out.append('released' if released else 'locked')
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 80', {'timer_s': 120, 'events': [[5, 'berth_occ'], [35, 'query'], [65, 'berth_occ'], [155, 'berth_occ'], [155, 'berth_occ'], [215, 'query']]}, ['locked', 'released']), ('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('control 1', {'timer_s': 45, 'events': [[5, 'overlap_occ'], [35, 'query'], [65, 'berth_clr'], [125, 'query'], [170, 'query'], [170, 'berth_clr'], [230, 'berth_occ'], [350, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 4', {'timer_s': 90, 'events': [[60, 'berth_clr'], [150, 'berth_occ'], [155, 'berth_occ'], [155, 'query']]}, ['locked']), ('control 7', {'timer_s': 45, 'events': [[5, 'query'], [65, 'overlap_occ'], [110, 'overlap_occ'], [170, 'query']]}, ['locked', 'locked'])], [('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 46', {'timer_s': 120, 'events': [[90, 'berth_clr'], [120, 'berth_clr'], [120, 'berth_occ'], [150, 'query'], [180, 'berth_occ'], [270, 'berth_occ'], [315, 'overlap_occ'], [375, 'query']]}, ['locked', 'released']), ('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('control 12', {'timer_s': 120, 'events': [[60, 'berth_occ'], [65, 'query'], [70, 'query'], [70, 'query'], [75, 'berth_occ'], [80, 'berth_occ'], [125, 'berth_clr'], [125, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 15', {'timer_s': 120, 'events': [[60, 'berth_clr'], [105, 'berth_clr'], [110, 'berth_clr'], [115, 'berth_clr'], [175, 'query'], [175, 'berth_clr'], [235, 'query'], [265, 'overlap_occ'], [270, 'berth_clr'], [390, 'query']]}, ['locked', 'locked', 'locked']), ('control 18', {'timer_s': 90, 'events': [[0, 'query'], [0, 'query'], [30, 'berth_occ'], [120, 'overlap_occ'], [210, 'berth_clr'], [215, 'query'], [245, 'berth_occ'], [365, 'query']]}, ['locked', 'locked', 'released', 'released'])], [('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('boundary: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('boundary: ninety second timer at one minute', {'timer_s': 90, 'events': [[0, 'berth_occ'], [60, 'query'], [89, 'query'], [90, 'query']]}, ['locked', 'locked', 'released']), ('control 23', {'timer_s': 45, 'events': [[0, 'query'], [5, 'berth_occ'], [95, 'berth_occ'], [185, 'berth_occ'], [245, 'query']]}, ['locked', 'released']), ('control 26', {'timer_s': 60, 'events': [[30, 'query'], [60, 'query'], [150, 'overlap_occ'], [150, 'query']]}, ['locked', 'locked', 'locked']), ('control 29', {'timer_s': 90, 'events': [[90, 'overlap_occ'], [95, 'query'], [95, 'berth_occ'], [185, 'query'], [230, 'query'], [260, 'query'], [350, 'berth_occ'], [410, 'query']]}, ['locked', 'locked', 'locked', 'locked', 'locked'])], [('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: ninety second timer at one minute', {'timer_s': 90, 'events': [[0, 'berth_occ'], [60, 'query'], [89, 'query'], [90, 'query']]}, ['locked', 'locked', 'released']), ('boundary: expiry after a minute boundary', {'timer_s': 120, 'events': [[30, 'berth_occ'], [140, 'query'], [150, 'query']]}, ['locked', 'released']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('control 34', {'timer_s': 60, 'events': [[5, 'query'], [35, 'query'], [80, 'berth_occ'], [170, 'berth_occ'], [260, 'berth_occ'], [320, 'berth_occ'], [350, 'overlap_occ'], [395, 'overlap_occ'], [440, 'query'], [500, 'query'], [560, 'query']]}, ['locked', 'locked', 'released', 'released', 'released']), ('control 37', {'timer_s': 120, 'events': [[30, 'query'], [60, 'overlap_occ'], [120, 'berth_occ'], [180, 'overlap_occ'], [185, 'overlap_occ'], [185, 'overlap_occ'], [245, 'query'], [275, 'query'], [280, 'overlap_occ'], [280, 'berth_occ'], [280, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 40', {'timer_s': 90, 'events': [[60, 'overlap_occ'], [90, 'query'], [135, 'berth_occ'], [195, 'overlap_occ'], [225, 'berth_occ'], [230, 'berth_clr'], [230, 'query']]}, ['locked', 'locked'])], [('regression: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 80', {'timer_s': 120, 'events': [[5, 'berth_occ'], [35, 'query'], [65, 'berth_occ'], [155, 'berth_occ'], [155, 'berth_occ'], [215, 'query']]}, ['locked', 'released']), ('regression: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('control 45', {'timer_s': 60, 'events': [[60, 'query'], [120, 'query'], [150, 'overlap_occ'], [155, 'berth_occ'], [245, 'overlap_occ'], [290, 'berth_occ'], [380, 'berth_clr'], [470, 'overlap_occ'], [470, 'query'], [475, 'berth_occ'], [595, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('control 48', {'timer_s': 45, 'events': [[60, 'overlap_occ'], [150, 'berth_occ'], [180, 'overlap_occ'], [180, 'berth_occ'], [270, 'berth_clr'], [275, 'berth_clr'], [275, 'overlap_occ'], [275, 'query']]}, ['locked']), ('control 51', {'timer_s': 90, 'events': [[90, 'berth_occ'], [150, 'berth_clr'], [195, 'query'], [255, 'berth_occ'], [315, 'query'], [320, 'query'], [320, 'overlap_occ'], [440, 'query']]}, ['locked', 'locked', 'locked', 'locked'])]]
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: track circuit bob restarts nothing['locked']['released']Failed
sampled regression 80['locked', 'released']['locked', 'released']Passed
regression: train stops at time zero['locked']['released']Failed
boundary: expiry exactly on time['released']['released']Passed
boundary: train leaves before expiry['locked']['locked']Passed
control 1['locked', 'locked', 'locked', 'locked']['locked', 'locked', 'locked', 'locked']Passed
control 4['locked']['locked']Passed
control 7['locked', 'locked']['locked', 'locked']Passed

SHA-256 / 9f6f2448571e47d0edf6a7e23e1d14d15727967827666a929cd7308d8ebbc7db

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

Case digest / f8f697d581b48055886ba7f8ed211633299f763dedbe3283cbba7dd06cc9a971