FAILURE MAP
← Case archive

FA-69031 / Tide and marine navigation tables / Open access

Course to steer across a stream: Speed made good ignores the along-track stream · case 01

Fair and foul streams give the same ETA.

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

ROOT CAUSE

SOG uses only the boat speed component along the track.

VERIFIED REPAIR

Add drift*cos(set-track) to the boat component.

Unsuccessful approach: Adding the along-track stream to the full boat speed ignores the crab angle loss.

Case contract

Input [track, boat_speed, set, drift] with degrees true (set is the direction the stream flows toward) and knots. Cross-track stream c=drift*sin(set-track); heading = track+asin(-c/boat); SOG=boat*cos(asin(-c/boat))+drift*cos(set-track). Return None when |c|>boat or SOG<=0, else [heading mod 360 rounded to 0.1, SOG rounded to 0.01].

Why this case matters

Tide tables and passage plans turn published predictions into go/no-go decisions about depth, clearance and timing.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    track,bs,setd,drift=x
    rel=math.radians(setd-track)
    cross=drift*math.sin(rel)
    if abs(cross)>bs: return None
    a=math.asin(-cross/bs)
    sog=bs*math.cos(a)
    if sog<=0: return None
    return [round(math.degrees(math.radians(track)+a)%360,1),round(sog,2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('course to steer across a stream [200, 4, 200, 3]', [200, 4, 200, 3], [200.0, 7.0]), ('course to steer across a stream [90, 6, 180, 1.5]', [90, 6, 180, 1.5], [75.5, 5.81]), ('course to steer across a stream [90, 6, 0, 1.5]', [90, 6, 0, 1.5], [104.5, 5.81]), ('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [200, 4, 20, 3]', [200, 4, 20, 3], [200.0, 1.0]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None)], [('course to steer across a stream [200, 4, 20, 3]', [200, 4, 20, 3], [200.0, 1.0]), ('course to steer across a stream [90, 6, 0, 1.5]', [90, 6, 0, 1.5], [104.5, 5.81]), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [200, 4, 200, 3]', [200, 4, 200, 3], [200.0, 7.0]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None), ('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None)], [('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None), ('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None), ('course to steer across a stream [0, 5, 90, 0]', [0, 5, 90, 0], [0.0, 5.0]), ('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [5, 6, 275, 1.2]', [5, 6, 275, 1.2], [16.5, 5.88])], [('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [0, 5, 90, 0]', [0, 5, 90, 0], [0.0, 5.0]), ('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [5, 6, 275, 1.2]', [5, 6, 275, 1.2], [16.5, 5.88]), ('course to steer across a stream [180, 3, 90, 2.9]', [180, 3, 90, 2.9], [255.2, 0.77]), ('course to steer across a stream [300, 7, 120, 2]', [300, 7, 120, 2], [300.0, 5.0])], [('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [180, 3, 90, 2.9]', [180, 3, 90, 2.9], [255.2, 0.77]), ('course to steer across a stream [300, 7, 120, 2]', [300, 7, 120, 2], [300.0, 5.0]), ('course to steer across a stream [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94]), ('course to steer across a stream [355, 6, 260, 1.5]', [355, 6, 260, 1.5], [9.4, 5.68]), ('course to steer across a stream [0, 5, 10, 7]', [0, 5, 10, 7], [345.9, 11.74])]]
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
course to steer across a stream [200, 4, 200, 3][200.0, 4.0][200.0, 7.0]Failed
course to steer across a stream [90, 6, 180, 1.5][75.5, 5.81][75.5, 5.81]Passed
course to steer across a stream [90, 6, 0, 1.5][104.5, 5.81][104.5, 5.81]Passed
course to steer across a stream [10, 5, 100, 2][346.4, 4.58][346.4, 4.58]Passed
course to steer across a stream [350, 5, 80, 2][326.4, 4.58][326.4, 4.58]Passed
course to steer across a stream [200, 4, 20, 3][200.0, 4.0][200.0, 1.0]Failed
course to steer across a stream [45, 5, 135, 6]NoneNonePassed

SHA-256 / dffbb39650b3b642132982c084c25363674ec24804f02a0e08014c3c1506966a

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    track,bs,setd,drift=x
    rel=math.radians(setd-track)
    cross=drift*math.sin(rel)
    if abs(cross)>bs: return None
    a=math.asin(-cross/bs)
    sog=bs+drift*math.cos(rel)
    if sog<=0: return None
    return [round(math.degrees(math.radians(track)+a)%360,1),round(sog,2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('course to steer across a stream [200, 4, 200, 3]', [200, 4, 200, 3], [200.0, 7.0]), ('course to steer across a stream [90, 6, 180, 1.5]', [90, 6, 180, 1.5], [75.5, 5.81]), ('course to steer across a stream [90, 6, 0, 1.5]', [90, 6, 0, 1.5], [104.5, 5.81]), ('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [200, 4, 20, 3]', [200, 4, 20, 3], [200.0, 1.0]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None)], [('course to steer across a stream [200, 4, 20, 3]', [200, 4, 20, 3], [200.0, 1.0]), ('course to steer across a stream [90, 6, 0, 1.5]', [90, 6, 0, 1.5], [104.5, 5.81]), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [200, 4, 200, 3]', [200, 4, 200, 3], [200.0, 7.0]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None), ('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None)], [('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None), ('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None), ('course to steer across a stream [0, 5, 90, 0]', [0, 5, 90, 0], [0.0, 5.0]), ('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [5, 6, 275, 1.2]', [5, 6, 275, 1.2], [16.5, 5.88])], [('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [0, 5, 90, 0]', [0, 5, 90, 0], [0.0, 5.0]), ('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [5, 6, 275, 1.2]', [5, 6, 275, 1.2], [16.5, 5.88]), ('course to steer across a stream [180, 3, 90, 2.9]', [180, 3, 90, 2.9], [255.2, 0.77]), ('course to steer across a stream [300, 7, 120, 2]', [300, 7, 120, 2], [300.0, 5.0])], [('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [180, 3, 90, 2.9]', [180, 3, 90, 2.9], [255.2, 0.77]), ('course to steer across a stream [300, 7, 120, 2]', [300, 7, 120, 2], [300.0, 5.0]), ('course to steer across a stream [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94]), ('course to steer across a stream [355, 6, 260, 1.5]', [355, 6, 260, 1.5], [9.4, 5.68]), ('course to steer across a stream [0, 5, 10, 7]', [0, 5, 10, 7], [345.9, 11.74])]]
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
course to steer across a stream [200, 4, 200, 3][200.0, 7.0][200.0, 7.0]Passed
course to steer across a stream [90, 6, 180, 1.5][75.5, 6.0][75.5, 5.81]Failed
course to steer across a stream [90, 6, 0, 1.5][104.5, 6.0][104.5, 5.81]Failed
course to steer across a stream [10, 5, 100, 2][346.4, 5.0][346.4, 4.58]Failed
course to steer across a stream [350, 5, 80, 2][326.4, 5.0][326.4, 4.58]Failed
course to steer across a stream [200, 4, 20, 3][200.0, 1.0][200.0, 1.0]Passed
course to steer across a stream [45, 5, 135, 6]NoneNonePassed

SHA-256 / 791ee6b2a0e06bfa712ce6bc1ab41790ce0a08417c6a7717bc8e2ccb492a8844

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    track,bs,setd,drift=x
    rel=math.radians(setd-track)
    cross=drift*math.sin(rel)
    if abs(cross)>bs: return None
    a=math.asin(-cross/bs)
    sog=bs*math.cos(a)+drift*math.cos(rel)
    if sog<=0: return None
    return [round(math.degrees(math.radians(track)+a)%360,1),round(sog,2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('course to steer across a stream [200, 4, 200, 3]', [200, 4, 200, 3], [200.0, 7.0]), ('course to steer across a stream [90, 6, 180, 1.5]', [90, 6, 180, 1.5], [75.5, 5.81]), ('course to steer across a stream [90, 6, 0, 1.5]', [90, 6, 0, 1.5], [104.5, 5.81]), ('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [200, 4, 20, 3]', [200, 4, 20, 3], [200.0, 1.0]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None)], [('course to steer across a stream [200, 4, 20, 3]', [200, 4, 20, 3], [200.0, 1.0]), ('course to steer across a stream [90, 6, 0, 1.5]', [90, 6, 0, 1.5], [104.5, 5.81]), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [200, 4, 200, 3]', [200, 4, 200, 3], [200.0, 7.0]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None), ('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None)], [('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('course to steer across a stream [45, 5, 135, 6]', [45, 5, 135, 6], None), ('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None), ('course to steer across a stream [0, 5, 90, 0]', [0, 5, 90, 0], [0.0, 5.0]), ('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [5, 6, 275, 1.2]', [5, 6, 275, 1.2], [16.5, 5.88])], [('course to steer across a stream [120, 5, 300, 5.5]', [120, 5, 300, 5.5], None), ('course to steer across a stream [350, 5, 80, 2]', [350, 5, 80, 2], [326.4, 4.58]), ('course to steer across a stream [0, 5, 90, 0]', [0, 5, 90, 0], [0.0, 5.0]), ('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [5, 6, 275, 1.2]', [5, 6, 275, 1.2], [16.5, 5.88]), ('course to steer across a stream [180, 3, 90, 2.9]', [180, 3, 90, 2.9], [255.2, 0.77]), ('course to steer across a stream [300, 7, 120, 2]', [300, 7, 120, 2], [300.0, 5.0])], [('course to steer across a stream [270, 8, 315, 2.5]', [270, 8, 315, 2.5], [257.2, 9.57]), ('course to steer across a stream [45, 5, 60, 7]', [45, 5, 60, 7], [23.8, 11.42]), ('course to steer across a stream [180, 3, 90, 2.9]', [180, 3, 90, 2.9], [255.2, 0.77]), ('course to steer across a stream [300, 7, 120, 2]', [300, 7, 120, 2], [300.0, 5.0]), ('course to steer across a stream [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94]), ('course to steer across a stream [355, 6, 260, 1.5]', [355, 6, 260, 1.5], [9.4, 5.68]), ('course to steer across a stream [0, 5, 10, 7]', [0, 5, 10, 7], [345.9, 11.74])]]
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
course to steer across a stream [200, 4, 200, 3][200.0, 7.0][200.0, 7.0]Passed
course to steer across a stream [90, 6, 180, 1.5][75.5, 5.81][75.5, 5.81]Passed
course to steer across a stream [90, 6, 0, 1.5][104.5, 5.81][104.5, 5.81]Passed
course to steer across a stream [10, 5, 100, 2][346.4, 4.58][346.4, 4.58]Passed
course to steer across a stream [350, 5, 80, 2][326.4, 4.58][326.4, 4.58]Passed
course to steer across a stream [200, 4, 20, 3][200.0, 1.0][200.0, 1.0]Passed
course to steer across a stream [45, 5, 135, 6]NoneNonePassed

SHA-256 / 3a27a3fff2050a8430b007ba0ad8020093539673d4b93c58267969ae07d1bbb1

Verification & scope

A deterministic toy model with stipulated rules and constants; not certified hydrographic software or a substitute for official tide tables. 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:07.701988+00:00.

Case digest / e76fe21417dc79505d9332bd48b5995f5366ce2ca7ec95196848d97d3f3d7cb2