FAILURE MAP
← Case archive

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

Course to steer across a stream: Headings near north are not wrapped · case 01

Courses left of north come out negative or above 360.

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

ROOT CAUSE

The heading is not reduced modulo 360.

VERIFIED REPAIR

Reduce the heading modulo 360.

Unsuccessful approach: Taking abs() of a negative heading reflects it about north.

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)+drift*math.cos(rel)
    if sog<=0: return None
    return [round(math.degrees(math.radians(track)+a),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 [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('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 [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 [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 [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94]), ('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 [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 [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 [355, 6, 260, 1.5]', [355, 6, 260, 1.5], [9.4, 5.68]), ('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 [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 [0, 5, 10, 7]', [0, 5, 10, 7], [345.9, 11.74]), ('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 [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94])], [('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('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]), ('course to steer across a stream [90, 4, 100, 4.5]', [90, 4, 100, 4.5], [78.7, 8.35])]]
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 [10, 5, 100, 2][-13.6, 4.58][346.4, 4.58]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 [350, 5, 80, 2][326.4, 4.58][326.4, 4.58]Passed
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 [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 / 6a0d9cb63cf618273fcf6f4aef3fbc3a75450e0154f1f7371bbeb98129e02772

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*math.cos(a)+drift*math.cos(rel)
    if sog<=0: return None
    return [round(abs(math.degrees(math.radians(track)+a)),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 [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('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 [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 [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 [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94]), ('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 [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 [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 [355, 6, 260, 1.5]', [355, 6, 260, 1.5], [9.4, 5.68]), ('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 [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 [0, 5, 10, 7]', [0, 5, 10, 7], [345.9, 11.74]), ('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 [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94])], [('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('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]), ('course to steer across a stream [90, 4, 100, 4.5]', [90, 4, 100, 4.5], [78.7, 8.35])]]
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 [10, 5, 100, 2][13.6, 4.58][346.4, 4.58]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 [350, 5, 80, 2][326.4, 4.58][326.4, 4.58]Passed
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 [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 / b887c1d6ab7abc51073dfa39ec2880728498613762750e4b2415e81484441abe

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 [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('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 [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 [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 [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94]), ('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 [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 [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 [355, 6, 260, 1.5]', [355, 6, 260, 1.5], [9.4, 5.68]), ('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 [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 [0, 5, 10, 7]', [0, 5, 10, 7], [345.9, 11.74]), ('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 [5, 6, 90, 1.5]', [5, 6, 90, 1.5], [350.6, 5.94])], [('course to steer across a stream [10, 5, 100, 2]', [10, 5, 100, 2], [346.4, 4.58]), ('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]), ('course to steer across a stream [90, 4, 100, 4.5]', [90, 4, 100, 4.5], [78.7, 8.35])]]
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 [10, 5, 100, 2][346.4, 4.58][346.4, 4.58]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 [350, 5, 80, 2][326.4, 4.58][326.4, 4.58]Passed
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 [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 / 49bcdd87140b27bafd6b5521ae3ae73a109b6af3389bba7abd471eb8ab2674cb

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

Case digest / af89e8e154e2a551762abc6fde78ec61732f17c8f610955cc6c485b8384a8e18