{"abstract":"Joints drift beyond their limits over repeated sweeps.","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":"Clamping the old joint value before adding the increment still allows the sum to exceed the limit.","family":"w2-inverse_kinematics_solvers-cyclic-coordinate-descent-joint-limit-clamp-target","id":"FA-87951","implementations":{"attempt":{"sha256":"de8d9b25f0b66f9ed7521392ea1baf715c5ac38673c9563c580a7ce4fe53e1ca","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]]], [['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]], ['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]]], [['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 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]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['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]], ['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]]]]\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":"868a3d317a077ca67482e2c2a2c4f269aadd3efc186604aba380e016ca9da2b0","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]=qs[j]+max(-lim,min(lim,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]]], [['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]], ['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]]], [['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 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]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['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]], ['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]]]]\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":"e6b523ea0f79c218c47435f8c2e4e1bdabc4d707d440ebcecd7ff4cfac5d78d3","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]]], [['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]], ['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]]], [['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 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]], ['limit blocks tip joint', [[[1, 1], [0, 0], [0, -1.8], 2, 0.001, 30]], [[-30.0, -30.0], 2, 1.4333]], ['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]], ['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]]]]\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-clamp-target","generated_at":"2026-09-29T14:51:03.665508+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":"Clamp qs[j]+ang into [-lim, lim].","root_cause":"The limit is applied to the incremental angle rather than to the resulting joint value.","sha256":"2f5b14d6a3970fc5e41c1243b0860572e6a1a7fc38bde3ba484f80c6878f7aa4","title":"CCD clamps the per-step rotation instead of the joint angle · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verification":{"attempt":{"elapsed_ms":39.851,"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.838,-101.135],3,0.2429],"check":"target behind base","expected":[[-121.779,-101.253],3,0.2445],"passed":false},{"actual":[[56.443,44.547,-22.35],3,0.3013],"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.838, -101.135], 3, 0.2429], \"expected\": [[-121.779, -101.253], 3, 0.2445], \"passed\": false}, {\"check\": \"tight limits\", \"actual\": [[56.443, 44.547, -22.35], 3, 0.3013], \"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"},"broken":{"elapsed_ms":43.41,"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.779,-101.253],3,0.2445],"check":"target behind base","expected":[[-121.779,-101.253],3,0.2445],"passed":true},{"actual":[[34.995,46.61,36.873],3,0.0594],"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.779, -101.253], 3, 0.2445], \"expected\": [[-121.779, -101.253], 3, 0.2445], \"passed\": true}, {\"check\": \"tight limits\", \"actual\": [[34.995, 46.61, 36.873], 3, 0.0594], \"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":39.778,"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"}