FA-87941 / Inverse kinematics solvers / Open access
CCD sweep visits joints base first · case 01
Converged configurations differ from the tip-first CCD contract and use more sweeps.
ROOT CAUSE
The joint loop runs from the base to the tip instead of from the tip to the base.
VERIFIED REPAIR
Iterate j from len-1 down to 0.
Unsuccessful approach: Stopping the reversed range at 1 skips the base joint entirely.
Case contract
Input [lengths, angles, target, iters, tol, lim]: planar serial chain with relative joint angles in degrees and a symmetric joint limit +/-lim degrees. Each iteration first stops if the tip is within tol of target, else counts one sweep that visits joints from the tip joint down to the base, re-evaluating forward kinematics before each joint, rotating by the signed angle between tip and target vectors and clamping the joint. Return [angles (3 decimals), sweeps performed, final error (4 decimals)].
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):
ls,qs,t,iters,tol,lim=x
qs=[math.radians(q) for q in qs]
lim=math.radians(lim)
def fk(qs):
pts=[(0.0,0.0)]
a=0.0
for l,q in zip(ls,qs):
a+=q
px,py=pts[-1]
pts.append((px+l*math.cos(a),py+l*math.sin(a)))
return pts
used=0
for it in range(iters):
pts=fk(qs)
ex,ey=pts[-1]
if math.hypot(t[0]-ex,t[1]-ey)<=tol: break
used+=1
for j in range(len(ls)):
pts=fk(qs)
jx,jy=pts[j]
ex,ey=pts[-1]
ve=(ex-jx,ey-jy)
vt=(t[0]-jx,t[1]-jy)
ang=math.atan2(ve[0]*vt[1]-ve[1]*vt[0],ve[0]*vt[0]+ve[1]*vt[1])
qs[j]=max(-lim,min(lim,qs[j]+ang))
pts=fk(qs)
err=math.hypot(t[0]-pts[-1][0],t[1]-pts[-1][1])
return [[round(math.degrees(q),3) for q in qs],used,round(err,4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]]], [['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]], ['four links curl', [[[1, 0.8, 0.6, 0.4], [20, 20, 20, 20], [0.3, 1.2], 3, 0.001, 120]], [[36.469, 53.792, 70.335, 118.779], 3, 0.001]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['zero iterations', [[[1, 1], [0, 0], [0, 2], 0, 0.001, 170]], [[0.0, 0.0], 0, 2.8284]]], [['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['four links curl', [[[1, 0.8, 0.6, 0.4], [20, 20, 20, 20], [0.3, 1.2], 3, 0.001, 120]], [[36.469, 53.792, 70.335, 118.779], 3, 0.001]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['zero iterations', [[[1, 1], [0, 0], [0, 2], 0, 0.001, 170]], [[0.0, 0.0], 0, 2.8284]], ['target crossing pi', [[[1, 1, 1], [170, 0, 0], [-2, -0.5], 2, 0.001, 180]], [[168.982, -6.514, 102.831], 2, 0.0176]], ['loose tolerance', [[[1, 1, 1], [5, 5, 5], [2.9, 0.4], 3, 0.2, 170]], [[5.0, 5.0, 5.0], 0, 0.1285]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]]], [['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['target crossing pi', [[[1, 1, 1], [170, 0, 0], [-2, -0.5], 2, 0.001, 180]], [[168.982, -6.514, 102.831], 2, 0.0176]], ['loose tolerance', [[[1, 1, 1], [5, 5, 5], [2.9, 0.4], 3, 0.2, 170]], [[5.0, 5.0, 5.0], 0, 0.1285]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]], ['tight elbow limit', [[[1, 1, 1], [0, 0, 0], [-1, 1], 3, 0.001, 60]], [[60.0, 60.0, 60.0], 3, 0.7321]]], [['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]]]]
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 |
|---|---|---|---|
| three links one sweep | [[45.0, 0.0, -0.0], 1, 0.8787] | [[5.996, 17.348, 108.435], 1, 0.3586] | Failed |
| three links several sweeps | [[83.521, 24.569, 67.931], 4, 0.1957] | [[63.011, 58.047, 42.064], 4, 0.0426] | Failed |
| target behind base | [[-172.405, 0.0], 3, 0.4867] | [[-121.779, -101.253], 3, 0.2445] | Failed |
| tight limits | [[41.824, 45.0, 22.982], 3, 0.1128] | [[36.006, 45.0, 37.294], 3, 0.0448] | Failed |
| already converged | [[0.0, 90.0], 0, 0.0] | [[0.0, 90.0], 0, 0.0] | Passed |
| converges early | [[26.565, 0.0], 10, 0.7639] | [[0.0, 90.0], 1, 0.0] | Failed |
| unreachable stretch | [[-6.607, 8.798], 3, 2.0088] | [[8.551, -17.103], 3, 2.0222] | Failed |
SHA-256 / 239a3a766a42b2057b591fc68939c485c67ef9410ac9d58bd706624546ab9976
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
ls,qs,t,iters,tol,lim=x
qs=[math.radians(q) for q in qs]
lim=math.radians(lim)
def fk(qs):
pts=[(0.0,0.0)]
a=0.0
for l,q in zip(ls,qs):
a+=q
px,py=pts[-1]
pts.append((px+l*math.cos(a),py+l*math.sin(a)))
return pts
used=0
for it in range(iters):
pts=fk(qs)
ex,ey=pts[-1]
if math.hypot(t[0]-ex,t[1]-ey)<=tol: break
used+=1
for j in range(len(ls)-1,0,-1):
pts=fk(qs)
jx,jy=pts[j]
ex,ey=pts[-1]
ve=(ex-jx,ey-jy)
vt=(t[0]-jx,t[1]-jy)
ang=math.atan2(ve[0]*vt[1]-ve[1]*vt[0],ve[0]*vt[0]+ve[1]*vt[1])
qs[j]=max(-lim,min(lim,qs[j]+ang))
pts=fk(qs)
err=math.hypot(t[0]-pts[-1][0],t[1]-pts[-1][1])
return [[round(math.degrees(q),3) for q in qs],used,round(err,4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]]], [['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]], ['four links curl', [[[1, 0.8, 0.6, 0.4], [20, 20, 20, 20], [0.3, 1.2], 3, 0.001, 120]], [[36.469, 53.792, 70.335, 118.779], 3, 0.001]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['zero iterations', [[[1, 1], [0, 0], [0, 2], 0, 0.001, 170]], [[0.0, 0.0], 0, 2.8284]]], [['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['four links curl', [[[1, 0.8, 0.6, 0.4], [20, 20, 20, 20], [0.3, 1.2], 3, 0.001, 120]], [[36.469, 53.792, 70.335, 118.779], 3, 0.001]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['zero iterations', [[[1, 1], [0, 0], [0, 2], 0, 0.001, 170]], [[0.0, 0.0], 0, 2.8284]], ['target crossing pi', [[[1, 1, 1], [170, 0, 0], [-2, -0.5], 2, 0.001, 180]], [[168.982, -6.514, 102.831], 2, 0.0176]], ['loose tolerance', [[[1, 1, 1], [5, 5, 5], [2.9, 0.4], 3, 0.2, 170]], [[5.0, 5.0, 5.0], 0, 0.1285]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]]], [['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['target crossing pi', [[[1, 1, 1], [170, 0, 0], [-2, -0.5], 2, 0.001, 180]], [[168.982, -6.514, 102.831], 2, 0.0176]], ['loose tolerance', [[[1, 1, 1], [5, 5, 5], [2.9, 0.4], 3, 0.2, 170]], [[5.0, 5.0, 5.0], 0, 0.1285]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]], ['tight elbow limit', [[[1, 1, 1], [0, 0, 0], [-1, 1], 3, 0.001, 60]], [[60.0, 60.0, 60.0], 3, 0.7321]]], [['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]]]]
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 |
|---|---|---|---|
| three links one sweep | [[0.0, 17.348, 108.435], 1, 0.4117] | [[5.996, 17.348, 108.435], 1, 0.3586] | Failed |
| three links several sweeps | [[10.0, 103.543, 47.675], 4, 0.8678] | [[63.011, 58.047, 42.064], 4, 0.0426] | Failed |
| target behind base | [[0.0, -175.0], 3, 1.508] | [[-121.779, -101.253], 3, 0.2445] | Failed |
| tight limits | [[0.0, 45.0, 45.0], 3, 1.4442] | [[36.006, 45.0, 37.294], 3, 0.0448] | Failed |
| already converged | [[0.0, 90.0], 0, 0.0] | [[0.0, 90.0], 0, 0.0] | Passed |
| converges early | [[0.0, 90.0], 1, 0.0] | [[0.0, 90.0], 1, 0.0] | Passed |
| unreachable stretch | [[30.0, -39.065], 3, 2.1736] | [[8.551, -17.103], 3, 2.0222] | Failed |
SHA-256 / db34153f73e25a660411ed106143d37472f4018b378a72e58f66294b57fcc9aa
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
ls,qs,t,iters,tol,lim=x
qs=[math.radians(q) for q in qs]
lim=math.radians(lim)
def fk(qs):
pts=[(0.0,0.0)]
a=0.0
for l,q in zip(ls,qs):
a+=q
px,py=pts[-1]
pts.append((px+l*math.cos(a),py+l*math.sin(a)))
return pts
used=0
for it in range(iters):
pts=fk(qs)
ex,ey=pts[-1]
if math.hypot(t[0]-ex,t[1]-ey)<=tol: break
used+=1
for j in range(len(ls)-1,-1,-1):
pts=fk(qs)
jx,jy=pts[j]
ex,ey=pts[-1]
ve=(ex-jx,ey-jy)
vt=(t[0]-jx,t[1]-jy)
ang=math.atan2(ve[0]*vt[1]-ve[1]*vt[0],ve[0]*vt[0]+ve[1]*vt[1])
qs[j]=max(-lim,min(lim,qs[j]+ang))
pts=fk(qs)
err=math.hypot(t[0]-pts[-1][0],t[1]-pts[-1][1])
return [[round(math.degrees(q),3) for q in qs],used,round(err,4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]]], [['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]], ['four links curl', [[[1, 0.8, 0.6, 0.4], [20, 20, 20, 20], [0.3, 1.2], 3, 0.001, 120]], [[36.469, 53.792, 70.335, 118.779], 3, 0.001]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['zero iterations', [[[1, 1], [0, 0], [0, 2], 0, 0.001, 170]], [[0.0, 0.0], 0, 2.8284]]], [['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['four links curl', [[[1, 0.8, 0.6, 0.4], [20, 20, 20, 20], [0.3, 1.2], 3, 0.001, 120]], [[36.469, 53.792, 70.335, 118.779], 3, 0.001]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['zero iterations', [[[1, 1], [0, 0], [0, 2], 0, 0.001, 170]], [[0.0, 0.0], 0, 2.8284]], ['target crossing pi', [[[1, 1, 1], [170, 0, 0], [-2, -0.5], 2, 0.001, 180]], [[168.982, -6.514, 102.831], 2, 0.0176]], ['loose tolerance', [[[1, 1, 1], [5, 5, 5], [2.9, 0.4], 3, 0.2, 170]], [[5.0, 5.0, 5.0], 0, 0.1285]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]]], [['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['target crossing pi', [[[1, 1, 1], [170, 0, 0], [-2, -0.5], 2, 0.001, 180]], [[168.982, -6.514, 102.831], 2, 0.0176]], ['loose tolerance', [[[1, 1, 1], [5, 5, 5], [2.9, 0.4], 3, 0.2, 170]], [[5.0, 5.0, 5.0], 0, 0.1285]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]], ['tight elbow limit', [[[1, 1, 1], [0, 0, 0], [-1, 1], 3, 0.001, 60]], [[60.0, 60.0, 60.0], 3, 0.7321]]], [['three links one sweep', [[[1, 1, 1], [0, 0, 0], [1.5, 1.5], 1, 0.001, 170]], [[5.996, 17.348, 108.435], 1, 0.3586]], ['three links several sweeps', [[[1, 1, 1], [10, 10, 10], [-1, 2], 4, 0.001, 170]], [[63.011, 58.047, 42.064], 4, 0.0426]], ['target behind base', [[[1, 1], [0, 0], [-1.5, -0.2], 3, 0.001, 175]], [[-121.779, -101.253], 3, 0.2445]], ['tight limits', [[[1, 1, 1], [0, 0, 0], [0.5, 2.5], 3, 0.001, 45]], [[36.006, 45.0, 37.294], 3, 0.0448]], ['already converged', [[[1, 1], [0, 90], [1, 1], 5, 0.001, 170]], [[0.0, 90.0], 0, 0.0]], ['converges early', [[[2, 1], [0, 0], [2, 1], 10, 0.01, 170]], [[0.0, 90.0], 1, 0.0]], ['unreachable stretch', [[[1, 1], [30, 30], [4, 0], 3, 0.001, 170]], [[8.551, -17.103], 3, 2.0222]]]]
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 |
|---|---|---|---|
| three links one sweep | [[5.996, 17.348, 108.435], 1, 0.3586] | [[5.996, 17.348, 108.435], 1, 0.3586] | Passed |
| three links several sweeps | [[63.011, 58.047, 42.064], 4, 0.0426] | [[63.011, 58.047, 42.064], 4, 0.0426] | Passed |
| target behind base | [[-121.779, -101.253], 3, 0.2445] | [[-121.779, -101.253], 3, 0.2445] | Passed |
| tight limits | [[36.006, 45.0, 37.294], 3, 0.0448] | [[36.006, 45.0, 37.294], 3, 0.0448] | Passed |
| already converged | [[0.0, 90.0], 0, 0.0] | [[0.0, 90.0], 0, 0.0] | Passed |
| converges early | [[0.0, 90.0], 1, 0.0] | [[0.0, 90.0], 1, 0.0] | Passed |
| unreachable stretch | [[8.551, -17.103], 3, 2.0222] | [[8.551, -17.103], 3, 2.0222] | Passed |
SHA-256 / 10f1dc6cc59eaa6006994c18f5281cba9dfb5bd74f80d059b6f8cb33f904592d
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:03.624503+00:00.
Case digest / 1b34444d27ef8acd488dace25934189758a65a25c1e672d1cfcde3addcde3305