FA-88251 / Inverse kinematics solvers / Open access
Initial branch choice is counted as a flip · case 01
Every trajectory reports one extra flip.
ROOT CAUSE
The flip test compares with the unset branch sentinel.
VERIFIED REPAIR
Count flips only when a previous branch exists and differs.
Unsuccessful approach: Counting only up-to-down transitions misses down-to-up flips.
Case contract
Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].
Why this case matters
Inverse kinematics code turns task-space goals into joint commands; a wrong branch, sign, limit or update order sends a real arm to the wrong pose.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
l1,l2,q0,targets,maxjump=x
prev=list(q0)
out=[]
flips=0
branch=None
for px,py in targets:
c=(px*px+py*py-l1*l1-l2*l2)/(2*l1*l2)
if abs(c)>1.0:
out.append('hold')
continue
sols=[]
for sgn in (1,-1):
s=sgn*math.sqrt(1-c*c)
t2=math.degrees(math.atan2(s,c))
t1=math.degrees(math.atan2(py,px)-math.atan2(l2*s,l1+l2*c))
sols.append([sgn,t1,t2])
def dist(sol):
return max(abs((sol[1]-prev[0]+180.0)%360.0-180.0),abs((sol[2]-prev[1]+180.0)%360.0-180.0))
best=min(sols,key=dist)
if dist(best)>maxjump:
out.append('jump')
continue
if best[0]!=branch: flips+=1
branch=best[0]
t1=prev[0]+((best[1]-prev[0]+180.0)%360.0-180.0)
t2=prev[1]+((best[2]-prev[1]+180.0)%360.0-180.0)
prev=[t1,t2]
out.append([round(t1,3),round(t2,3)])
return [out,flips]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['smooth arc', [[1, 1, [0, 90], [[1, 1], [0.9, 1.1], [0.8, 1.2]], 30]], [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0]], ['seam crossing', [[1, 1, [170, 20], [[-1.5, 0.3], [-1.5, -0.3], [-1.4, -0.6]], 40]], [['jump', 'jump', 'jump'], 0]], ['multi-turn start', [[1, 1, [360, 90], [[1, 1], [1.1, 0.9]], 30]], [[[360.0, 90.0], [354.576, 89.427]], 0]], ['dropout in middle', [[1, 1, [0, 90], [[1, 1], [3, 0], [0.9, 1.1]], 30]], [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0]], ['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]]], [['multi-turn start', [[1, 1, [360, 90], [[1, 1], [1.1, 0.9]], 30]], [[[360.0, 90.0], [354.576, 89.427]], 0]], ['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 0]], ['flip through straight', [[1, 1, [0, 30], [[1.9, 0.3], [2, 0], [1.9, -0.3], [1.8, -0.6]], 60]], [[[-6.922, 31.788], [0.0, 0.0], [-24.867, 31.788], [-36.87, 36.87]], 0]], ['start near down branch', [[1, 1, [60, -60], [[1.2, 1.0], [1.3, 0.9]], 50]], [[[78.451, -77.291], [72.456, -75.522]], 0]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]]], [['dropout in middle', [[1, 1, [0, 90], [[1, 1], [3, 0], [0.9, 1.1]], 30]], [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0]], ['flip through straight', [[1, 1, [0, 30], [[1.9, 0.3], [2, 0], [1.9, -0.3], [1.8, -0.6]], 60]], [[[-6.922, 31.788], [0.0, 0.0], [-24.867, 31.788], [-36.87, 36.87]], 0]], ['start near down branch', [[1, 1, [60, -60], [[1.2, 1.0], [1.3, 0.9]], 50]], [[[78.451, -77.291], [72.456, -75.522]], 0]], ['big jump budget', [[1, 1.5, [0, 0], [[-1, 1.5], [1.5, -1], [0.5, 2]], 360]], [[[67.38, 90.0], [-90.0, 90.0], [32.65, 70.529]], 0]], ['negative seam elbow', [[1, 1, [-170, -20], [[-1.5, -0.3], [-1.5, 0.3], [-1.3, 0.8]], 45]], [['jump', 'jump', 'jump'], 0]], ['all unreachable', [[1, 1, [0, 0], [[3, 3], [0, 5]], 30]], [['hold', 'hold'], 0]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]]], [['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['negative seam elbow', [[1, 1, [-170, -20], [[-1.5, -0.3], [-1.5, 0.3], [-1.3, 0.8]], 45]], [['jump', 'jump', 'jump'], 0]], ['all unreachable', [[1, 1, [0, 0], [[3, 3], [0, 5]], 30]], [['hold', 'hold'], 0]], ['jump then recover', [[1, 1, [0, 120], [[0.2, 1.0], [1.8, 0.2], [0.3, 1.0]], 60]], [[[19.347, 118.685], 'jump', [14.768, 117.065]], 0]], ['second target flips', [[1.5, 1, [0, 45], [[2, 1.2], [2.2, -0.4]], 150]], [[[13.925, 43.114], [-31.604, 54.315]], 0]], ['dropout across branch change', [[1, 1, [-93, 83], [[1.9, -0.3], [3, 3], [-1.5, -1.3]], 120]], [[[-24.867, 31.788], 'hold', [-132.051, -14.07]], 1]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]]], [['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['second target flips', [[1.5, 1, [0, 45], [[2, 1.2], [2.2, -0.4]], 150]], [[[13.925, 43.114], [-31.604, 54.315]], 0]], ['dropout across branch change', [[1, 1, [-93, 83], [[1.9, -0.3], [3, 3], [-1.5, -1.3]], 120]], [[[-24.867, 31.788], 'hold', [-132.051, -14.07]], 1]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]], ['rejected then flip', [[1, 1, [171, 101], [[0.8, -1.3], [0.8, 0.7], [0.2, -1.1]], 56]], [['jump', 'jump', [224.293, 112.024]], 0]], ['rejection mid sweep', [[1, 1, [37, 154], [[0.4, 1.3], [-0.5, -0.6], [-0.8, 1.4]], 82]], [[[25.747, 94.301], 'jump', [83.474, 72.542]], 0]]]]
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 |
|---|---|---|---|
| smooth arc | [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 1] | [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0] | Failed |
| seam crossing | [['jump', 'jump', 'jump'], 0] | [['jump', 'jump', 'jump'], 0] | Passed |
| multi-turn start | [[[360.0, 90.0], [354.576, 89.427]], 1] | [[[360.0, 90.0], [354.576, 89.427]], 0] | Failed |
| dropout in middle | [[[0.0, 90.0], 'hold', [5.997, 89.427]], 1] | [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0] | Failed |
| dropout then branch change | [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 1] | [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0] | Failed |
| large jump rejected | [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 1] | [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0] | Failed |
| down to up flip | [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 2] | [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1] | Failed |
SHA-256 / 6b09241810ca4b122fb5f541d55eec53d1c3075ef6e0de8eda0951c5d4281a1e
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
l1,l2,q0,targets,maxjump=x
prev=list(q0)
out=[]
flips=0
branch=None
for px,py in targets:
c=(px*px+py*py-l1*l1-l2*l2)/(2*l1*l2)
if abs(c)>1.0:
out.append('hold')
continue
sols=[]
for sgn in (1,-1):
s=sgn*math.sqrt(1-c*c)
t2=math.degrees(math.atan2(s,c))
t1=math.degrees(math.atan2(py,px)-math.atan2(l2*s,l1+l2*c))
sols.append([sgn,t1,t2])
def dist(sol):
return max(abs((sol[1]-prev[0]+180.0)%360.0-180.0),abs((sol[2]-prev[1]+180.0)%360.0-180.0))
best=min(sols,key=dist)
if dist(best)>maxjump:
out.append('jump')
continue
if branch is not None and best[0]<branch: flips+=1
branch=best[0]
t1=prev[0]+((best[1]-prev[0]+180.0)%360.0-180.0)
t2=prev[1]+((best[2]-prev[1]+180.0)%360.0-180.0)
prev=[t1,t2]
out.append([round(t1,3),round(t2,3)])
return [out,flips]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['smooth arc', [[1, 1, [0, 90], [[1, 1], [0.9, 1.1], [0.8, 1.2]], 30]], [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0]], ['seam crossing', [[1, 1, [170, 20], [[-1.5, 0.3], [-1.5, -0.3], [-1.4, -0.6]], 40]], [['jump', 'jump', 'jump'], 0]], ['multi-turn start', [[1, 1, [360, 90], [[1, 1], [1.1, 0.9]], 30]], [[[360.0, 90.0], [354.576, 89.427]], 0]], ['dropout in middle', [[1, 1, [0, 90], [[1, 1], [3, 0], [0.9, 1.1]], 30]], [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0]], ['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]]], [['multi-turn start', [[1, 1, [360, 90], [[1, 1], [1.1, 0.9]], 30]], [[[360.0, 90.0], [354.576, 89.427]], 0]], ['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 0]], ['flip through straight', [[1, 1, [0, 30], [[1.9, 0.3], [2, 0], [1.9, -0.3], [1.8, -0.6]], 60]], [[[-6.922, 31.788], [0.0, 0.0], [-24.867, 31.788], [-36.87, 36.87]], 0]], ['start near down branch', [[1, 1, [60, -60], [[1.2, 1.0], [1.3, 0.9]], 50]], [[[78.451, -77.291], [72.456, -75.522]], 0]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]]], [['dropout in middle', [[1, 1, [0, 90], [[1, 1], [3, 0], [0.9, 1.1]], 30]], [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0]], ['flip through straight', [[1, 1, [0, 30], [[1.9, 0.3], [2, 0], [1.9, -0.3], [1.8, -0.6]], 60]], [[[-6.922, 31.788], [0.0, 0.0], [-24.867, 31.788], [-36.87, 36.87]], 0]], ['start near down branch', [[1, 1, [60, -60], [[1.2, 1.0], [1.3, 0.9]], 50]], [[[78.451, -77.291], [72.456, -75.522]], 0]], ['big jump budget', [[1, 1.5, [0, 0], [[-1, 1.5], [1.5, -1], [0.5, 2]], 360]], [[[67.38, 90.0], [-90.0, 90.0], [32.65, 70.529]], 0]], ['negative seam elbow', [[1, 1, [-170, -20], [[-1.5, -0.3], [-1.5, 0.3], [-1.3, 0.8]], 45]], [['jump', 'jump', 'jump'], 0]], ['all unreachable', [[1, 1, [0, 0], [[3, 3], [0, 5]], 30]], [['hold', 'hold'], 0]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]]], [['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['negative seam elbow', [[1, 1, [-170, -20], [[-1.5, -0.3], [-1.5, 0.3], [-1.3, 0.8]], 45]], [['jump', 'jump', 'jump'], 0]], ['all unreachable', [[1, 1, [0, 0], [[3, 3], [0, 5]], 30]], [['hold', 'hold'], 0]], ['jump then recover', [[1, 1, [0, 120], [[0.2, 1.0], [1.8, 0.2], [0.3, 1.0]], 60]], [[[19.347, 118.685], 'jump', [14.768, 117.065]], 0]], ['second target flips', [[1.5, 1, [0, 45], [[2, 1.2], [2.2, -0.4]], 150]], [[[13.925, 43.114], [-31.604, 54.315]], 0]], ['dropout across branch change', [[1, 1, [-93, 83], [[1.9, -0.3], [3, 3], [-1.5, -1.3]], 120]], [[[-24.867, 31.788], 'hold', [-132.051, -14.07]], 1]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]]], [['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['second target flips', [[1.5, 1, [0, 45], [[2, 1.2], [2.2, -0.4]], 150]], [[[13.925, 43.114], [-31.604, 54.315]], 0]], ['dropout across branch change', [[1, 1, [-93, 83], [[1.9, -0.3], [3, 3], [-1.5, -1.3]], 120]], [[[-24.867, 31.788], 'hold', [-132.051, -14.07]], 1]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]], ['rejected then flip', [[1, 1, [171, 101], [[0.8, -1.3], [0.8, 0.7], [0.2, -1.1]], 56]], [['jump', 'jump', [224.293, 112.024]], 0]], ['rejection mid sweep', [[1, 1, [37, 154], [[0.4, 1.3], [-0.5, -0.6], [-0.8, 1.4]], 82]], [[[25.747, 94.301], 'jump', [83.474, 72.542]], 0]]]]
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 |
|---|---|---|---|
| smooth arc | [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0] | [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0] | Passed |
| seam crossing | [['jump', 'jump', 'jump'], 0] | [['jump', 'jump', 'jump'], 0] | Passed |
| multi-turn start | [[[360.0, 90.0], [354.576, 89.427]], 0] | [[[360.0, 90.0], [354.576, 89.427]], 0] | Passed |
| dropout in middle | [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0] | [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0] | Passed |
| dropout then branch change | [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0] | [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0] | Passed |
| large jump rejected | [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0] | [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0] | Passed |
| down to up flip | [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 0] | [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1] | Failed |
SHA-256 / 5a500cab6850c2b5a287f6b5b4c1e811c3e3bccf05a063f1a4cf00ac0e76d660
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
l1,l2,q0,targets,maxjump=x
prev=list(q0)
out=[]
flips=0
branch=None
for px,py in targets:
c=(px*px+py*py-l1*l1-l2*l2)/(2*l1*l2)
if abs(c)>1.0:
out.append('hold')
continue
sols=[]
for sgn in (1,-1):
s=sgn*math.sqrt(1-c*c)
t2=math.degrees(math.atan2(s,c))
t1=math.degrees(math.atan2(py,px)-math.atan2(l2*s,l1+l2*c))
sols.append([sgn,t1,t2])
def dist(sol):
return max(abs((sol[1]-prev[0]+180.0)%360.0-180.0),abs((sol[2]-prev[1]+180.0)%360.0-180.0))
best=min(sols,key=dist)
if dist(best)>maxjump:
out.append('jump')
continue
if branch is not None and best[0]!=branch: flips+=1
branch=best[0]
t1=prev[0]+((best[1]-prev[0]+180.0)%360.0-180.0)
t2=prev[1]+((best[2]-prev[1]+180.0)%360.0-180.0)
prev=[t1,t2]
out.append([round(t1,3),round(t2,3)])
return [out,flips]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['smooth arc', [[1, 1, [0, 90], [[1, 1], [0.9, 1.1], [0.8, 1.2]], 30]], [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0]], ['seam crossing', [[1, 1, [170, 20], [[-1.5, 0.3], [-1.5, -0.3], [-1.4, -0.6]], 40]], [['jump', 'jump', 'jump'], 0]], ['multi-turn start', [[1, 1, [360, 90], [[1, 1], [1.1, 0.9]], 30]], [[[360.0, 90.0], [354.576, 89.427]], 0]], ['dropout in middle', [[1, 1, [0, 90], [[1, 1], [3, 0], [0.9, 1.1]], 30]], [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0]], ['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]]], [['multi-turn start', [[1, 1, [360, 90], [[1, 1], [1.1, 0.9]], 30]], [[[360.0, 90.0], [354.576, 89.427]], 0]], ['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 0]], ['flip through straight', [[1, 1, [0, 30], [[1.9, 0.3], [2, 0], [1.9, -0.3], [1.8, -0.6]], 60]], [[[-6.922, 31.788], [0.0, 0.0], [-24.867, 31.788], [-36.87, 36.87]], 0]], ['start near down branch', [[1, 1, [60, -60], [[1.2, 1.0], [1.3, 0.9]], 50]], [[[78.451, -77.291], [72.456, -75.522]], 0]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]]], [['dropout in middle', [[1, 1, [0, 90], [[1, 1], [3, 0], [0.9, 1.1]], 30]], [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0]], ['flip through straight', [[1, 1, [0, 30], [[1.9, 0.3], [2, 0], [1.9, -0.3], [1.8, -0.6]], 60]], [[[-6.922, 31.788], [0.0, 0.0], [-24.867, 31.788], [-36.87, 36.87]], 0]], ['start near down branch', [[1, 1, [60, -60], [[1.2, 1.0], [1.3, 0.9]], 50]], [[[78.451, -77.291], [72.456, -75.522]], 0]], ['big jump budget', [[1, 1.5, [0, 0], [[-1, 1.5], [1.5, -1], [0.5, 2]], 360]], [[[67.38, 90.0], [-90.0, 90.0], [32.65, 70.529]], 0]], ['negative seam elbow', [[1, 1, [-170, -20], [[-1.5, -0.3], [-1.5, 0.3], [-1.3, 0.8]], 45]], [['jump', 'jump', 'jump'], 0]], ['all unreachable', [[1, 1, [0, 0], [[3, 3], [0, 5]], 30]], [['hold', 'hold'], 0]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]]], [['dropout then branch change', [[1, 1, [0, 60], [[1.5, 0.8], [3, 3], [1.6, -0.6]], 200]], [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0]], ['negative seam elbow', [[1, 1, [-170, -20], [[-1.5, -0.3], [-1.5, 0.3], [-1.3, 0.8]], 45]], [['jump', 'jump', 'jump'], 0]], ['all unreachable', [[1, 1, [0, 0], [[3, 3], [0, 5]], 30]], [['hold', 'hold'], 0]], ['jump then recover', [[1, 1, [0, 120], [[0.2, 1.0], [1.8, 0.2], [0.3, 1.0]], 60]], [[[19.347, 118.685], 'jump', [14.768, 117.065]], 0]], ['second target flips', [[1.5, 1, [0, 45], [[2, 1.2], [2.2, -0.4]], 150]], [[[13.925, 43.114], [-31.604, 54.315]], 0]], ['dropout across branch change', [[1, 1, [-93, 83], [[1.9, -0.3], [3, 3], [-1.5, -1.3]], 120]], [[[-24.867, 31.788], 'hold', [-132.051, -14.07]], 1]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]]], [['large jump rejected', [[1, 1, [0, 90], [[1, 1], [-1, -1], [1, 0.9]], 20]], [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0]], ['second target flips', [[1.5, 1, [0, 45], [[2, 1.2], [2.2, -0.4]], 150]], [[[13.925, 43.114], [-31.604, 54.315]], 0]], ['dropout across branch change', [[1, 1, [-93, 83], [[1.9, -0.3], [3, 3], [-1.5, -1.3]], 120]], [[[-24.867, 31.788], 'hold', [-132.051, -14.07]], 1]], ['down to up flip', [[1, 1, [69, -23], [[0.3, -0.8], [1.9, -0.9], [0.1, 0.9]], 164]], [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1]], ['wide sweep flips', [[1, 1, [-5, -54], [[0.1, -1.7], [0.7, 1.5], [-1.2, 0.5]], 154]], [[[-55.005, -63.256], [30.841, 68.284], [107.922, 98.917]], 1]], ['rejected then flip', [[1, 1, [171, 101], [[0.8, -1.3], [0.8, 0.7], [0.2, -1.1]], 56]], [['jump', 'jump', [224.293, 112.024]], 0]], ['rejection mid sweep', [[1, 1, [37, 154], [[0.4, 1.3], [-0.5, -0.6], [-0.8, 1.4]], 82]], [[[25.747, 94.301], 'jump', [83.474, 72.542]], 0]]]]
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 |
|---|---|---|---|
| smooth arc | [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0] | [[[0.0, 90.0], [5.997, 89.427], [12.456, 87.708]], 0] | Passed |
| seam crossing | [['jump', 'jump', 'jump'], 0] | [['jump', 'jump', 'jump'], 0] | Passed |
| multi-turn start | [[[360.0, 90.0], [354.576, 89.427]], 0] | [[[360.0, 90.0], [354.576, 89.427]], 0] | Passed |
| dropout in middle | [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0] | [[[0.0, 90.0], 'hold', [5.997, 89.427]], 0] | Passed |
| dropout then branch change | [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0] | [[[-3.716, 63.577], 'hold', [-51.862, 62.613]], 0] | Passed |
| large jump rejected | [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0] | [[[0.0, 90.0], 'jump', [-5.738, 95.451]], 0] | Passed |
| down to up flip | [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1] | [[[-4.734, -129.42], 'hold', [20.581, -233.843]], 1] | Passed |
SHA-256 / e1add712eb2ef41a37ad91a265f4f0d6f3643969e8871b326c51cd309366277e
Verification & scope
Deterministic planar or low-dimensional teaching model with a stipulated convention; not a general robotics library. 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:51:06.383184+00:00.
Case digest / 70ce56344ee701255eb7e09256f30708610aa93ca338760fbe9116c0a94b20db