FA-88301 / Inverse kinematics solvers / Open access
Gear ratio divides the joint angle · case 01
Motor commands are ratio^2 too small.
ROOT CAUSE
The joint-to-motor conversion divides by the gear ratio instead of multiplying.
VERIFIED REPAIR
Multiply the joint angle by its own ratio.
Unsuccessful approach: Using the first joint ratio for every joint mis-scales the others.
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]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['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]], ['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]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['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]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]]]]
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 | [0, -1] | [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 | [60] | [240] | Failed |
| limit exceeded | [10, 0] | ['limit', 0, 100000] | Failed |
| within limit exactly | [0, 1] | [0, 1] | Passed |
SHA-256 / 494e9fd6b78f0bcf6bf36c28f4a9364c1087cd2f30750927aed4ff6a7715b509
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=dirs[i]*(geo[i]-off[i])*ratio[0]
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]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['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]], ['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]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['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]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]]]]
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, -1389] | [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 | [240] | [240] | Passed |
| limit exceeded | ['limit', 0, 100000] | ['limit', 0, 100000] | Passed |
| within limit exactly | [0, 1] | [0, 1] | Passed |
SHA-256 / 1883cfa6584f05bfea98246ca18e8752227fd0238a9681f91ca5f8ffe198cad8
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]], ['limit exceeded', [[[90, 0], [0, 0], [1, 1], [100, 1], [1000, 1000], [[-1000, 1000], [-10, 10]]]], ['limit', 0, 100000]], ['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]], ['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]], ['geo within range counts out', [[[20], [0], [1], [50], [100], [[-100, 1000]]]], ['limit', 0, 1111]]], [['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]], ['gear down joint', [[[180, 45], [0, 0], [1, 1], [0.5, 4], [100, 100], [[-100000, 100000], [-100000, 100000]]]], [100, 200]]]]
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 / b02e45c60180ffa5796ec07c2a9cea788eb37b004170703cd502483495d847b6
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.886989+00:00.
Case digest / 906f903c2eb8b8dd804dfda1a67f0e1b68b687f0aa834297693791a8941287d7