FA-67036 / Railway interlocking logic / Open access
Overlap timed release: overrun blocking · case 01
The overlap is released while the train stands in it.
ROOT CAUSE
Entry into the overlap is ignored.
VERIFIED REPAIR
Mark an overrun whenever the overlap is entered before release.
Unsuccessful approach: Only counting overruns when no timer is running misses the train that stopped briefly and rolled on.
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':
if start is None:
start = t
elif ev == 'berth_clr':
if not released:
start = None
elif ev == 'overlap_occ':
pass
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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 6', {'timer_s': 120, 'events': [[90, 'query'], [95, 'berth_clr'], [100, 'overlap_occ'], [130, 'berth_occ'], [130, 'overlap_occ'], [220, 'query'], [280, 'berth_clr'], [285, 'query'], [345, 'berth_clr'], [405, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('sampled regression 11', {'timer_s': 120, 'events': [[5, 'berth_occ'], [65, 'overlap_occ'], [125, 'query'], [185, 'query']]}, ['locked', 'locked']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 20', {'timer_s': 90, 'events': [[5, 'overlap_occ'], [95, 'overlap_occ'], [100, 'overlap_occ'], [160, 'query'], [220, 'berth_occ'], [340, 'query']]}, ['locked', 'locked']), ('sampled regression 42', {'timer_s': 120, 'events': [[90, 'berth_occ'], [180, 'overlap_occ'], [240, 'berth_clr'], [330, 'query'], [390, 'query']]}, ['locked', 'locked']), ('boundary: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 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']), ('sampled regression 77', {'timer_s': 120, 'events': [[60, 'query'], [120, 'berth_occ'], [165, 'overlap_occ'], [170, 'query'], [175, 'berth_occ'], [205, 'query'], [205, 'berth_occ'], [250, 'query'], [250, 'query']]}, ['locked', 'locked', 'locked', 'locked', 'locked']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('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']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 47', {'timer_s': 45, 'events': [[45, 'berth_occ'], [45, 'overlap_occ'], [50, 'query'], [95, 'query'], [95, 'query']]}, ['locked', 'locked', 'locked']), ('sampled regression 36', {'timer_s': 120, 'events': [[60, 'berth_clr'], [65, 'berth_occ'], [125, 'berth_occ'], [130, 'berth_occ'], [160, 'berth_occ'], [165, 'overlap_occ'], [210, 'overlap_occ'], [270, 'berth_occ'], [315, 'berth_occ'], [320, 'overlap_occ'], [320, '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']), ('boundary: expiry after a minute boundary', {'timer_s': 120, 'events': [[30, 'berth_occ'], [140, 'query'], [150, 'query']]}, ['locked', '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']), ('sampled regression 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']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 73', {'timer_s': 60, 'events': [[5, 'overlap_occ'], [50, 'berth_occ'], [140, 'berth_clr'], [200, 'berth_occ'], [260, 'query'], [290, 'overlap_occ'], [290, 'overlap_occ'], [290, 'query']]}, ['locked', 'locked']), ('sampled regression 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']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 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']), ('sampled regression 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 54', {'timer_s': 60, 'events': [[5, 'berth_occ'], [95, 'overlap_occ'], [140, 'query'], [145, 'query'], [205, 'query'], [205, 'overlap_occ'], [265, 'query']]}, ['released', 'released', 'released', 'released'])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: overrun into the overlap | ['released'] | ['locked'] | Failed |
| sampled regression 6 | ['locked', 'locked', 'released', 'released'] | ['locked', 'locked', 'locked', 'locked'] | Failed |
| sampled regression 11 | ['released', 'released'] | ['locked', 'locked'] | Failed |
| boundary: expiry exactly on time | ['released'] | ['released'] | Passed |
| boundary: track circuit bob restarts nothing | ['released'] | ['released'] | Passed |
| sampled regression 1 | ['locked', 'locked', 'locked', 'released'] | ['locked', 'locked', 'locked', 'locked'] | Failed |
| control 4 | ['locked'] | ['locked'] | Passed |
| control 7 | ['locked', 'locked'] | ['locked', 'locked'] | Passed |
SHA-256 / 7e32205460846fbaefa93054bf446f38dac0fc4c34187d23e7bd7cb167d6ed50
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':
if start is None:
start = t
elif ev == 'berth_clr':
if not released:
start = None
elif ev == 'overlap_occ':
if start is None:
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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 6', {'timer_s': 120, 'events': [[90, 'query'], [95, 'berth_clr'], [100, 'overlap_occ'], [130, 'berth_occ'], [130, 'overlap_occ'], [220, 'query'], [280, 'berth_clr'], [285, 'query'], [345, 'berth_clr'], [405, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('sampled regression 11', {'timer_s': 120, 'events': [[5, 'berth_occ'], [65, 'overlap_occ'], [125, 'query'], [185, 'query']]}, ['locked', 'locked']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 20', {'timer_s': 90, 'events': [[5, 'overlap_occ'], [95, 'overlap_occ'], [100, 'overlap_occ'], [160, 'query'], [220, 'berth_occ'], [340, 'query']]}, ['locked', 'locked']), ('sampled regression 42', {'timer_s': 120, 'events': [[90, 'berth_occ'], [180, 'overlap_occ'], [240, 'berth_clr'], [330, 'query'], [390, 'query']]}, ['locked', 'locked']), ('boundary: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 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']), ('sampled regression 77', {'timer_s': 120, 'events': [[60, 'query'], [120, 'berth_occ'], [165, 'overlap_occ'], [170, 'query'], [175, 'berth_occ'], [205, 'query'], [205, 'berth_occ'], [250, 'query'], [250, 'query']]}, ['locked', 'locked', 'locked', 'locked', 'locked']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('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']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 47', {'timer_s': 45, 'events': [[45, 'berth_occ'], [45, 'overlap_occ'], [50, 'query'], [95, 'query'], [95, 'query']]}, ['locked', 'locked', 'locked']), ('sampled regression 36', {'timer_s': 120, 'events': [[60, 'berth_clr'], [65, 'berth_occ'], [125, 'berth_occ'], [130, 'berth_occ'], [160, 'berth_occ'], [165, 'overlap_occ'], [210, 'overlap_occ'], [270, 'berth_occ'], [315, 'berth_occ'], [320, 'overlap_occ'], [320, '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']), ('boundary: expiry after a minute boundary', {'timer_s': 120, 'events': [[30, 'berth_occ'], [140, 'query'], [150, 'query']]}, ['locked', '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']), ('sampled regression 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']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 73', {'timer_s': 60, 'events': [[5, 'overlap_occ'], [50, 'berth_occ'], [140, 'berth_clr'], [200, 'berth_occ'], [260, 'query'], [290, 'overlap_occ'], [290, 'overlap_occ'], [290, 'query']]}, ['locked', 'locked']), ('sampled regression 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']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 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']), ('sampled regression 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 54', {'timer_s': 60, 'events': [[5, 'berth_occ'], [95, 'overlap_occ'], [140, 'query'], [145, 'query'], [205, 'query'], [205, 'overlap_occ'], [265, 'query']]}, ['released', 'released', 'released', 'released'])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: overrun into the overlap | ['released'] | ['locked'] | Failed |
| sampled regression 6 | ['locked', 'locked', 'locked', 'locked'] | ['locked', 'locked', 'locked', 'locked'] | Passed |
| sampled regression 11 | ['released', 'released'] | ['locked', 'locked'] | Failed |
| boundary: expiry exactly on time | ['released'] | ['released'] | Passed |
| boundary: track circuit bob restarts nothing | ['released'] | ['released'] | Passed |
| sampled regression 1 | ['locked', 'locked', 'locked', 'locked'] | ['locked', 'locked', 'locked', 'locked'] | Passed |
| control 4 | ['locked'] | ['locked'] | Passed |
| control 7 | ['locked', 'locked'] | ['locked', 'locked'] | Passed |
SHA-256 / 9aa08e1aa07c9338eae7585c1f8c38709398fcbfdce1741c00a107473199d714
3 / The verified repair
Exit 0"""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':
if start is None:
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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 6', {'timer_s': 120, 'events': [[90, 'query'], [95, 'berth_clr'], [100, 'overlap_occ'], [130, 'berth_occ'], [130, 'overlap_occ'], [220, 'query'], [280, 'berth_clr'], [285, 'query'], [345, 'berth_clr'], [405, 'query']]}, ['locked', 'locked', 'locked', 'locked']), ('sampled regression 11', {'timer_s': 120, 'events': [[5, 'berth_occ'], [65, 'overlap_occ'], [125, 'query'], [185, 'query']]}, ['locked', 'locked']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 20', {'timer_s': 90, 'events': [[5, 'overlap_occ'], [95, 'overlap_occ'], [100, 'overlap_occ'], [160, 'query'], [220, 'berth_occ'], [340, 'query']]}, ['locked', 'locked']), ('sampled regression 42', {'timer_s': 120, 'events': [[90, 'berth_occ'], [180, 'overlap_occ'], [240, 'berth_clr'], [330, 'query'], [390, 'query']]}, ['locked', 'locked']), ('boundary: train stops at time zero', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_occ'], [60, 'query']]}, ['released']), ('boundary: train leaves before expiry', {'timer_s': 60, 'events': [[0, 'berth_occ'], [30, 'berth_clr'], [90, 'query']]}, ['locked']), ('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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 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']), ('sampled regression 77', {'timer_s': 120, 'events': [[60, 'query'], [120, 'berth_occ'], [165, 'overlap_occ'], [170, 'query'], [175, 'berth_occ'], [205, 'query'], [205, 'berth_occ'], [250, 'query'], [250, 'query']]}, ['locked', 'locked', 'locked', 'locked', 'locked']), ('boundary: berth clears after release', {'timer_s': 60, 'events': [[0, 'berth_occ'], [60, 'query'], [65, 'berth_clr'], [70, 'query']]}, ['released', 'released']), ('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']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 47', {'timer_s': 45, 'events': [[45, 'berth_occ'], [45, 'overlap_occ'], [50, 'query'], [95, 'query'], [95, 'query']]}, ['locked', 'locked', 'locked']), ('sampled regression 36', {'timer_s': 120, 'events': [[60, 'berth_clr'], [65, 'berth_occ'], [125, 'berth_occ'], [130, 'berth_occ'], [160, 'berth_occ'], [165, 'overlap_occ'], [210, 'overlap_occ'], [270, 'berth_occ'], [315, 'berth_occ'], [320, 'overlap_occ'], [320, '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']), ('boundary: expiry after a minute boundary', {'timer_s': 120, 'events': [[30, 'berth_occ'], [140, 'query'], [150, 'query']]}, ['locked', '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']), ('sampled regression 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']), ('sampled regression 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: overrun into the overlap', {'timer_s': 60, 'events': [[0, 'berth_occ'], [20, 'overlap_occ'], [120, 'query']]}, ['locked']), ('sampled regression 73', {'timer_s': 60, 'events': [[5, 'overlap_occ'], [50, 'berth_occ'], [140, 'berth_clr'], [200, 'berth_occ'], [260, 'query'], [290, 'overlap_occ'], [290, 'overlap_occ'], [290, 'query']]}, ['locked', 'locked']), ('sampled regression 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']), ('boundary: expiry exactly on time', {'timer_s': 60, 'events': [[10, 'berth_occ'], [70, 'query']]}, ['released']), ('boundary: track circuit bob restarts nothing', {'timer_s': 60, 'events': [[0, 'berth_occ'], [40, 'berth_occ'], [60, 'query']]}, ['released']), ('sampled regression 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']), ('sampled regression 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 54', {'timer_s': 60, 'events': [[5, 'berth_occ'], [95, 'overlap_occ'], [140, 'query'], [145, 'query'], [205, 'query'], [205, 'overlap_occ'], [265, 'query']]}, ['released', 'released', 'released', 'released'])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: overrun into the overlap | ['locked'] | ['locked'] | Passed |
| sampled regression 6 | ['locked', 'locked', 'locked', 'locked'] | ['locked', 'locked', 'locked', 'locked'] | Passed |
| sampled regression 11 | ['locked', 'locked'] | ['locked', 'locked'] | Passed |
| boundary: expiry exactly on time | ['released'] | ['released'] | Passed |
| boundary: track circuit bob restarts nothing | ['released'] | ['released'] | Passed |
| sampled regression 1 | ['locked', 'locked', 'locked', 'locked'] | ['locked', 'locked', 'locked', 'locked'] | Passed |
| control 4 | ['locked'] | ['locked'] | Passed |
| control 7 | ['locked', 'locked'] | ['locked', 'locked'] | Passed |
SHA-256 / f2acd74f78254c3400ce9ca3f5a1da9460b630c69e0f901e9b06da50e26e57b7
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.188674+00:00.
Case digest / bb0f6aceea20ce2508928bcee3efdd83dec47f7b472c27f3c6f77946b301e06b