FA-88311 / Inverse kinematics solvers / Open access
Joint direction flag reverses only the raw angle · case 01
Reversed joints get the zero offset applied with the wrong sign.
ROOT CAUSE
The direction multiplies geo before the offset is subtracted.
VERIFIED REPAIR
Apply the direction to the offset-corrected angle.
Unsuccessful approach: Applying the direction to the offset only reverses the zero instead of the motion.
Case contract
Input [geo,off,dirs,ratio,lines,lims], all per-joint lists: IK joint angles (degrees) become encoder counts per joint as counts = floor(dirs*(geo-off)*ratio/360*lines*4 + 0.5) (quadrature gives 4 counts per line, half rounds up). lims holds inclusive [lo,hi] count limits; the first violating joint returns ["limit", i, counts].
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):
geo,off,dirs,ratio,lines,lims=x
out=[]
for i in range(len(geo)):
m=(dirs[i]*geo[i]-off[i])*ratio[i]
cnt=math.floor(m/360.0*lines[i]*4+0.5)
lo,hi=lims[i]
if cnt<lo or cnt>hi: return ['limit',i,cnt]
out.append(cnt)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]]], [['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]], ['offset makes negative', [[[10], [40], [1], [3], [360], [[-5000, 5000]]]], [-360]]], [['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]], ['offset makes negative', [[[10], [40], [1], [3], [360], [[-5000, 5000]]]], [-360]], ['motor degrees small counts large', [[[5], [0], [1], [100], [1000], [[-5000, 5000]]]], ['limit', 0, 5556]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['motor degrees small counts large', [[[5], [0], [1], [100], [1000], [[-5000, 5000]]]], ['limit', 0, 5556]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]]]]
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 |
|---|---|---|---|
| two joints nominal | [278, -500] | [278, -833] | Failed |
| half count rounding up | [3] | [3] | Passed |
| negative fractional count | [-3] | [-3] | Passed |
| negative half rounds up | [-5] | [-5] | Passed |
| reversed joint with offset | [-480] | [240] | Failed |
| limit exceeded | ['limit', 0, 100000] | ['limit', 0, 100000] | Passed |
| within limit exactly | [0, 1] | [0, 1] | Passed |
SHA-256 / 5975a660e2b03f3d208d5b8e1ae9634b23c81945e226099f93a7a57cf1d18d96
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
geo,off,dirs,ratio,lines,lims=x
out=[]
for i in range(len(geo)):
m=(geo[i]-dirs[i]*off[i])*ratio[i]
cnt=math.floor(m/360.0*lines[i]*4+0.5)
lo,hi=lims[i]
if cnt<lo or cnt>hi: return ['limit',i,cnt]
out.append(cnt)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]]], [['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]], ['offset makes negative', [[[10], [40], [1], [3], [360], [[-5000, 5000]]]], [-360]]], [['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]], ['offset makes negative', [[[10], [40], [1], [3], [360], [[-5000, 5000]]]], [-360]], ['motor degrees small counts large', [[[5], [0], [1], [100], [1000], [[-5000, 5000]]]], ['limit', 0, 5556]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['motor degrees small counts large', [[[5], [0], [1], [100], [1000], [[-5000, 5000]]]], ['limit', 0, 5556]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]]]]
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 |
|---|---|---|---|
| two joints nominal | [278, 500] | [278, -833] | Failed |
| half count rounding up | [3] | [3] | Passed |
| negative fractional count | [-3] | [-3] | Passed |
| negative half rounds up | [-5] | [-5] | Passed |
| reversed joint with offset | [480] | [240] | Failed |
| limit exceeded | ['limit', 0, 100000] | ['limit', 0, 100000] | Passed |
| within limit exactly | [0, 1] | [0, 1] | Passed |
SHA-256 / d83ff0c900d81db2e46c13a7d95612d6ee1e18c2ddcc1f60516dffca894ec0b4
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
geo,off,dirs,ratio,lines,lims=x
out=[]
for i in range(len(geo)):
m=dirs[i]*(geo[i]-off[i])*ratio[i]
cnt=math.floor(m/360.0*lines[i]*4+0.5)
lo,hi=lims[i]
if cnt<lo or cnt>hi: return ['limit',i,cnt]
out.append(cnt)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]]], [['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]], ['offset makes negative', [[[10], [40], [1], [3], [360], [[-5000, 5000]]]], [-360]]], [['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]], ['offset makes negative', [[[10], [40], [1], [3], [360], [[-5000, 5000]]]], [-360]], ['motor degrees small counts large', [[[5], [0], [1], [100], [1000], [[-5000, 5000]]]], ['limit', 0, 5556]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['two joints nominal', [[[10, 20], [5, -5], [1, -1], [50, 30], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [278, -833]], ['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['motor degrees small counts large', [[[5], [0], [1], [100], [1000], [[-5000, 5000]]]], ['limit', 0, 5556]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['half count rounding up', [[[45], [0], [1], [1], [5], [[-10, 10]]]], [3]], ['negative fractional count', [[[-45], [0], [1], [1], [5.4], [[-10, 10]]]], [-3]], ['negative half rounds up', [[[-45], [0], [1], [1], [11], [[-10, 10]]]], [-5]], ['reversed joint with offset', [[[30], [90], [-1], [2], [180], [[-10000, 10000]]]], [240]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['within limit exactly', [[[0, 9], [0, 0], [1, 1], [1, 1], [10, 10], [[-1, 1], [-1, 1]]]], [0, 1]], ['three joint arm', [[[45, -30, 12], [0, 10, -3], [1, -1, 1], [100, 80, 50], [500, 500, 256], [[-1000000, 1000000], [-1000000, 1000000], [-1000000, 1000000]]]], [25000, 17778, 2133]]]]
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 |
|---|---|---|---|
| two joints nominal | [278, -833] | [278, -833] | Passed |
| half count rounding up | [3] | [3] | Passed |
| negative fractional count | [-3] | [-3] | Passed |
| negative half rounds up | [-5] | [-5] | Passed |
| reversed joint with offset | [240] | [240] | Passed |
| limit exceeded | ['limit', 0, 100000] | ['limit', 0, 100000] | Passed |
| within limit exactly | [0, 1] | [0, 1] | Passed |
SHA-256 / 77091929c7285f84fcf5f9dcf0749fac3e427a4ca63640dab55b3f927aaa6942
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:07.005724+00:00.
Case digest / 906cf00515a790bb8f594cacd8856003770be2db9f1ae23e47859bdac4cee067