{"abstract":"Joint limits are effectively disabled because 45 is treated as 45 radians.","category":"Inverse kinematics solvers","checks":7,"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)].","evaluation_group":"w2-inverse_kinematics_solvers-cyclic-coordinate-descent","failed_approach":"Halving the converted limit treats a symmetric bound as a total range.","family":"w2-inverse_kinematics_solvers-cyclic-coordinate-descent-joint-limit-units","id":"FA-87961","implementations":{"attempt":{"sha256":"1f4d8d17c2775fc9bf9e2d641ce928e064dbca7c306e891d4098fa27d0939fca","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\nimport math\nN = 1\nobservations = []\ndef solve(x):\n    ls,qs,t,iters,tol,lim=x\n    qs=[math.radians(q) for q in qs]\n    lim=math.radians(lim)/2\n    def fk(qs):\n        pts=[(0.0,0.0)]\n        a=0.0\n        for l,q in zip(ls,qs):\n            a+=q\n            px,py=pts[-1]\n            pts.append((px+l*math.cos(a),py+l*math.sin(a)))\n        return pts\n    used=0\n    for it in range(iters):\n        pts=fk(qs)\n        ex,ey=pts[-1]\n        if math.hypot(t[0]-ex,t[1]-ey)<=tol: break\n        used+=1\n        for j in range(len(ls)-1,-1,-1):\n            pts=fk(qs)\n            jx,jy=pts[j]\n            ex,ey=pts[-1]\n            ve=(ex-jx,ey-jy)\n            vt=(t[0]-jx,t[1]-jy)\n            ang=math.atan2(ve[0]*vt[1]-ve[1]*vt[0],ve[0]*vt[0]+ve[1]*vt[1])\n            qs[j]=max(-lim,min(lim,qs[j]+ang))\n    pts=fk(qs)\n    err=math.hypot(t[0]-pts[-1][0],t[1]-pts[-1][1])\n    return [[round(math.degrees(q),3) for q in qs],used,round(err,4)]\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['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]], ['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]], ['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]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['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]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"broken":{"sha256":"77f2703404f6437a5ab46d5c3379cdac488486d4dfbb9100ac03df156977dcab","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\nimport math\nN = 1\nobservations = []\ndef solve(x):\n    ls,qs,t,iters,tol,lim=x\n    qs=[math.radians(q) for q in qs]\n    lim=lim\n    def fk(qs):\n        pts=[(0.0,0.0)]\n        a=0.0\n        for l,q in zip(ls,qs):\n            a+=q\n            px,py=pts[-1]\n            pts.append((px+l*math.cos(a),py+l*math.sin(a)))\n        return pts\n    used=0\n    for it in range(iters):\n        pts=fk(qs)\n        ex,ey=pts[-1]\n        if math.hypot(t[0]-ex,t[1]-ey)<=tol: break\n        used+=1\n        for j in range(len(ls)-1,-1,-1):\n            pts=fk(qs)\n            jx,jy=pts[j]\n            ex,ey=pts[-1]\n            ve=(ex-jx,ey-jy)\n            vt=(t[0]-jx,t[1]-jy)\n            ang=math.atan2(ve[0]*vt[1]-ve[1]*vt[0],ve[0]*vt[0]+ve[1]*vt[1])\n            qs[j]=max(-lim,min(lim,qs[j]+ang))\n    pts=fk(qs)\n    err=math.hypot(t[0]-pts[-1][0],t[1]-pts[-1][1])\n    return [[round(math.degrees(q),3) for q in qs],used,round(err,4)]\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['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]], ['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]], ['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]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['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]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"fixed":{"sha256":"67cd5eedcb41d0745bc3c20a11dd7f308e7e54d816bc4a2bad5d1fe0f10a2659","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\nimport math\nN = 1\nobservations = []\ndef solve(x):\n    ls,qs,t,iters,tol,lim=x\n    qs=[math.radians(q) for q in qs]\n    lim=math.radians(lim)\n    def fk(qs):\n        pts=[(0.0,0.0)]\n        a=0.0\n        for l,q in zip(ls,qs):\n            a+=q\n            px,py=pts[-1]\n            pts.append((px+l*math.cos(a),py+l*math.sin(a)))\n        return pts\n    used=0\n    for it in range(iters):\n        pts=fk(qs)\n        ex,ey=pts[-1]\n        if math.hypot(t[0]-ex,t[1]-ey)<=tol: break\n        used+=1\n        for j in range(len(ls)-1,-1,-1):\n            pts=fk(qs)\n            jx,jy=pts[j]\n            ex,ey=pts[-1]\n            ve=(ex-jx,ey-jy)\n            vt=(t[0]-jx,t[1]-jy)\n            ang=math.atan2(ve[0]*vt[1]-ve[1]*vt[0],ve[0]*vt[0]+ve[1]*vt[1])\n            qs[j]=max(-lim,min(lim,qs[j]+ang))\n    pts=fk(qs)\n    err=math.hypot(t[0]-pts[-1][0],t[1]-pts[-1][1])\n    return [[round(math.degrees(q),3) for q in qs],used,round(err,4)]\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['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]], ['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]], ['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]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['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]], ['negative limits start', [[[1.5, 1], [-60, -60], [1, 1.5], 2, 0.001, 90]], [[90.0, -90.0], 1, 0.0]]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"}},"limitations":"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.","method":"Deterministic executable model with adversarial boundary fixtures.","provenance":{"created_by":"Failure Map","dependencies":"Python standard library","family":"w2-inverse_kinematics_solvers-cyclic-coordinate-descent-joint-limit-units","generated_at":"2026-09-29T14:51:03.797316+00:00","license":"CC0-1.0","python":"3.12.14","seed":1,"split":"open-access"},"relevance":"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.","repair":"Convert lim with math.radians.","root_cause":"The limit argument is not converted to radians before clamping.","sha256":"f919da5377dc5a7a7bdb744d1fff21dee77671ab1844fda1cb881c148f872262","title":"CCD joint limit in degrees is compared with radian angles · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verification":{"attempt":{"elapsed_ms":41.183,"exit_code":1,"observations":[{"actual":[[1.348,29.065,85.0],1,0.0948],"check":"three links one sweep","expected":[[5.996,17.348,108.435],1,0.3586],"passed":false},{"actual":[[48.977,85.0,21.287],4,0.1202],"check":"three links several sweeps","expected":[[63.011,58.047,42.064],4,0.0426],"passed":false},{"actual":[[-87.5,-87.5],3,1.0416],"check":"target behind base","expected":[[-121.779,-101.253],3,0.2445],"passed":false},{"actual":[[22.5,22.5,22.5],3,1.5899],"check":"tight limits","expected":[[36.006,45.0,37.294],3,0.0448],"passed":false},{"actual":[[0.0,90.0],0,0.0],"check":"already converged","expected":[[0.0,90.0],0,0.0],"passed":true},{"actual":[[1.05,85.0],10,0.0766],"check":"converges early","expected":[[0.0,90.0],1,0.0],"passed":false},{"actual":[[8.551,-17.103],3,2.0222],"check":"unreachable stretch","expected":[[8.551,-17.103],3,2.0222],"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"three links one sweep\", \"actual\": [[1.348, 29.065, 85.0], 1, 0.0948], \"expected\": [[5.996, 17.348, 108.435], 1, 0.3586], \"passed\": false}, {\"check\": \"three links several sweeps\", \"actual\": [[48.977, 85.0, 21.287], 4, 0.1202], \"expected\": [[63.011, 58.047, 42.064], 4, 0.0426], \"passed\": false}, {\"check\": \"target behind base\", \"actual\": [[-87.5, -87.5], 3, 1.0416], \"expected\": [[-121.779, -101.253], 3, 0.2445], \"passed\": false}, {\"check\": \"tight limits\", \"actual\": [[22.5, 22.5, 22.5], 3, 1.5899], \"expected\": [[36.006, 45.0, 37.294], 3, 0.0448], \"passed\": false}, {\"check\": \"already converged\", \"actual\": [[0.0, 90.0], 0, 0.0], \"expected\": [[0.0, 90.0], 0, 0.0], \"passed\": true}, {\"check\": \"converges early\", \"actual\": [[1.05, 85.0], 10, 0.0766], \"expected\": [[0.0, 90.0], 1, 0.0], \"passed\": false}, {\"check\": \"unreachable stretch\", \"actual\": [[8.551, -17.103], 3, 2.0222], \"expected\": [[8.551, -17.103], 3, 2.0222], \"passed\": true}], \"passed\": false}\n"},"broken":{"elapsed_ms":40.422,"exit_code":1,"observations":[{"actual":[[5.996,17.348,108.435],1,0.3586],"check":"three links one sweep","expected":[[5.996,17.348,108.435],1,0.3586],"passed":true},{"actual":[[63.011,58.047,42.064],4,0.0426],"check":"three links several sweeps","expected":[[63.011,58.047,42.064],4,0.0426],"passed":true},{"actual":[[-121.747,-101.317],3,0.2454],"check":"target behind base","expected":[[-121.779,-101.253],3,0.2445],"passed":false},{"actual":[[32.595,46.683,44.666],3,0.1521],"check":"tight limits","expected":[[36.006,45.0,37.294],3,0.0448],"passed":false},{"actual":[[0.0,90.0],0,0.0],"check":"already converged","expected":[[0.0,90.0],0,0.0],"passed":true},{"actual":[[0.0,90.0],1,0.0],"check":"converges early","expected":[[0.0,90.0],1,0.0],"passed":true},{"actual":[[8.551,-17.103],3,2.0222],"check":"unreachable stretch","expected":[[8.551,-17.103],3,2.0222],"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"three links one sweep\", \"actual\": [[5.996, 17.348, 108.435], 1, 0.3586], \"expected\": [[5.996, 17.348, 108.435], 1, 0.3586], \"passed\": true}, {\"check\": \"three links several sweeps\", \"actual\": [[63.011, 58.047, 42.064], 4, 0.0426], \"expected\": [[63.011, 58.047, 42.064], 4, 0.0426], \"passed\": true}, {\"check\": \"target behind base\", \"actual\": [[-121.747, -101.317], 3, 0.2454], \"expected\": [[-121.779, -101.253], 3, 0.2445], \"passed\": false}, {\"check\": \"tight limits\", \"actual\": [[32.595, 46.683, 44.666], 3, 0.1521], \"expected\": [[36.006, 45.0, 37.294], 3, 0.0448], \"passed\": false}, {\"check\": \"already converged\", \"actual\": [[0.0, 90.0], 0, 0.0], \"expected\": [[0.0, 90.0], 0, 0.0], \"passed\": true}, {\"check\": \"converges early\", \"actual\": [[0.0, 90.0], 1, 0.0], \"expected\": [[0.0, 90.0], 1, 0.0], \"passed\": true}, {\"check\": \"unreachable stretch\", \"actual\": [[8.551, -17.103], 3, 2.0222], \"expected\": [[8.551, -17.103], 3, 2.0222], \"passed\": true}], \"passed\": false}\n"},"fixed":{"elapsed_ms":46.145,"exit_code":0,"observations":[{"actual":[[5.996,17.348,108.435],1,0.3586],"check":"three links one sweep","expected":[[5.996,17.348,108.435],1,0.3586],"passed":true},{"actual":[[63.011,58.047,42.064],4,0.0426],"check":"three links several sweeps","expected":[[63.011,58.047,42.064],4,0.0426],"passed":true},{"actual":[[-121.779,-101.253],3,0.2445],"check":"target behind base","expected":[[-121.779,-101.253],3,0.2445],"passed":true},{"actual":[[36.006,45.0,37.294],3,0.0448],"check":"tight limits","expected":[[36.006,45.0,37.294],3,0.0448],"passed":true},{"actual":[[0.0,90.0],0,0.0],"check":"already converged","expected":[[0.0,90.0],0,0.0],"passed":true},{"actual":[[0.0,90.0],1,0.0],"check":"converges early","expected":[[0.0,90.0],1,0.0],"passed":true},{"actual":[[8.551,-17.103],3,2.0222],"check":"unreachable stretch","expected":[[8.551,-17.103],3,2.0222],"passed":true}],"passed":true,"stderr":"","stdout":"{\"observations\": [{\"check\": \"three links one sweep\", \"actual\": [[5.996, 17.348, 108.435], 1, 0.3586], \"expected\": [[5.996, 17.348, 108.435], 1, 0.3586], \"passed\": true}, {\"check\": \"three links several sweeps\", \"actual\": [[63.011, 58.047, 42.064], 4, 0.0426], \"expected\": [[63.011, 58.047, 42.064], 4, 0.0426], \"passed\": true}, {\"check\": \"target behind base\", \"actual\": [[-121.779, -101.253], 3, 0.2445], \"expected\": [[-121.779, -101.253], 3, 0.2445], \"passed\": true}, {\"check\": \"tight limits\", \"actual\": [[36.006, 45.0, 37.294], 3, 0.0448], \"expected\": [[36.006, 45.0, 37.294], 3, 0.0448], \"passed\": true}, {\"check\": \"already converged\", \"actual\": [[0.0, 90.0], 0, 0.0], \"expected\": [[0.0, 90.0], 0, 0.0], \"passed\": true}, {\"check\": \"converges early\", \"actual\": [[0.0, 90.0], 1, 0.0], \"expected\": [[0.0, 90.0], 1, 0.0], \"passed\": true}, {\"check\": \"unreachable stretch\", \"actual\": [[8.551, -17.103], 3, 2.0222], \"expected\": [[8.551, -17.103], 3, 2.0222], \"passed\": true}], \"passed\": true}\n"}},"verified":true,"visibility":"public"}