FA-88246 / Inverse kinematics solvers / Open access
Trajectory jump test is applied to the first solution rather than the chosen one · case 01
Waypoints are rejected as jumps even though the other branch is close.
ROOT CAUSE
The jump check measures the first computed branch instead of the best one, or treats the limit as exclusive.
VERIFIED REPAIR
Reject only when the chosen solution changes by more than maxjump.
Unsuccessful approach: Rejecting a change equal to maxjump contradicts the inclusive limit.
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(sols[0])>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]], ['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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]]], [['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]], ['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]], ['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]]], [['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]], ['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]], ['dropout near base', [[1, 1, [146, 142], [[-0.1, -1.0], [3, 3], [-0.3, 0.2]], 120]], [[[204.455, 119.67], 'hold', [225.924, 200.772]], 1]]], [['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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]], ['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]], ['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]]], [['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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 |
| jump exactly at limit | [[[0.0, 0.0], [0.0, 90.0]], 0] | [[[0.0, 0.0], [0.0, 90.0]], 0] | Passed |
| start near down branch | [['jump', 'jump'], 0] | [[[78.451, -77.291], [72.456, -75.522]], 0] | Failed |
SHA-256 / 354169ece4c20ef711b4c6e70ba3821e3360b5a8cefa92effc185fe1a2ccd0e5
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]], ['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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]]], [['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]], ['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]], ['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]]], [['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]], ['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]], ['dropout near base', [[1, 1, [146, 142], [[-0.1, -1.0], [3, 3], [-0.3, 0.2]], 120]], [[[204.455, 119.67], 'hold', [225.924, 200.772]], 1]]], [['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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]], ['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]], ['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]]], [['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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 |
| jump exactly at limit | [[[0.0, 0.0], 'jump'], 0] | [[[0.0, 0.0], [0.0, 90.0]], 0] | Failed |
| start near down branch | [[[78.451, -77.291], [72.456, -75.522]], 0] | [[[78.451, -77.291], [72.456, -75.522]], 0] | Passed |
SHA-256 / b7448e91cda935d023f8a5d88c3911059cc3c0ca7f65bbb458a575f0177cade0
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]], ['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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]]], [['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]], ['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]], ['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]]], [['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]], ['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]], ['dropout near base', [[1, 1, [146, 142], [[-0.1, -1.0], [3, 3], [-0.3, 0.2]], 120]], [[[204.455, 119.67], 'hold', [225.924, 200.772]], 1]]], [['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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]], ['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]], ['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]]], [['jump exactly at limit', [[2, 1, [0, 0], [[3, 0], [2, 1]], 90]], [[[0.0, 0.0], [0.0, 90.0]], 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 |
| jump exactly at limit | [[[0.0, 0.0], [0.0, 90.0]], 0] | [[[0.0, 0.0], [0.0, 90.0]], 0] | Passed |
| start near down branch | [[[78.451, -77.291], [72.456, -75.522]], 0] | [[[78.451, -77.291], [72.456, -75.522]], 0] | Passed |
SHA-256 / 67ac318ed956d3f9403512bc6555804ed0d28156fcdee7dd4855183fd7e496ea
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.370330+00:00.
Case digest / 815ae8290c60cfd3287dcfce19e83aa47ebcaee761b8fa0c607ca444ddd3edbf