broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    l1,l2,px,py,elbow=x
    r2=px*px+py*py
    reach=l1+l2
    if r2>reach*reach+1e-9: return 'unreachable'
    if r2<(l1-l2)**2-1e-9: return 'unreachable'
    c=(r2-l1*l1-l2*l2)/(2*l1*l2)
    c=max(-1.0,min(1.0,c))
    s=math.sqrt(1-c*c)
    if elbow=='down': s=-s
    q2=math.atan2(s,c)
    q1=math.atan2(py,px)+math.atan2(l2*s,l1+l2*c)
    d1=math.degrees(q1)
    d1=(d1+180.0)%360.0-180.0
    return [round(d1,4),round(math.degrees(q2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['general elbow up', [[2, 1, 2, 1, 'up']], [0.0, 90.0]], ['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable']], [['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]]], [['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]]], [['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]]], [['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]], ['upper left down', [[3, 2, -1, 4, 'down']], [131.2512, -70.5288]], ['near full reach', [[5, 3, 7.9, 0.5, 'down']], [10.0523, -17.1893]]]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    l1,l2,px,py,elbow=x
    r2=px*px+py*py
    reach=l1+l2
    if r2>reach*reach+1e-9: return 'unreachable'
    if r2<(l1-l2)**2-1e-9: return 'unreachable'
    c=(r2-l1*l1-l2*l2)/(2*l1*l2)
    c=max(-1.0,min(1.0,c))
    s=math.sqrt(1-c*c)
    if elbow=='down': s=-s
    q2=math.atan2(s,c)
    q1=math.atan2(py,px)-math.atan2(l2*c,l1+l2*s)
    d1=math.degrees(q1)
    d1=(d1+180.0)%360.0-180.0
    return [round(d1,4),round(math.degrees(q2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['general elbow up', [[2, 1, 2, 1, 'up']], [0.0, 90.0]], ['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable']], [['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]]], [['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]]], [['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]]], [['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]], ['upper left down', [[3, 2, -1, 4, 'down']], [131.2512, -70.5288]], ['near full reach', [[5, 3, 7.9, 0.5, 'down']], [10.0523, -17.1893]]]]
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)

prompt:
The shoulder points past the target by twice the forearm offset angle.

Input [l1,l2,px,py,elbow] with positive link lengths. Return [q1,q2] in degrees (4 decimals) for a planar two-link arm whose end effector reaches (px,py); elbow "up" gives q2>=0, "down" gives q2<=0; q1 is wrapped to [-180,180). Points outside the annulus |l1-l2|<=r<=l1+l2 (1e-9 slack on r squared) return "unreachable".

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    l1,l2,px,py,elbow=x
    r2=px*px+py*py
    reach=l1+l2
    if r2>reach*reach+1e-9: return 'unreachable'
    if r2<(l1-l2)**2-1e-9: return 'unreachable'
    c=(r2-l1*l1-l2*l2)/(2*l1*l2)
    c=max(-1.0,min(1.0,c))
    s=math.sqrt(1-c*c)
    if elbow=='down': s=-s
    q2=math.atan2(s,c)
    q1=math.atan2(px,py)-math.atan2(l2*s,l1+l2*c)
    d1=math.degrees(q1)
    d1=(d1+180.0)%360.0-180.0
    return [round(d1,4),round(math.degrees(q2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['general elbow up', [[2, 1, 2, 1, 'up']], [0.0, 90.0]], ['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]]], [['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]]], [['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]]], [['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]]], [['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]], ['upper left down', [[3, 2, -1, 4, 'down']], [131.2512, -70.5288]], ['near full reach', [[5, 3, 7.9, 0.5, 'down']], [10.0523, -17.1893]]]]
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)

meaning: Fixture-count band, not measured model difficulty

deps: stdlib

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    l1,l2,px,py,elbow=x
    r2=px*px+py*py
    reach=l1+l2
    if r2>reach*reach+1e-9: return 'unreachable'
    if r2<(l1-l2)**2-1e-9: return 'unreachable'
    c=(r2-l1*l1-l2*l2)/(2*l1*l2)
    c=max(-1.0,min(1.0,c))
    s=math.sqrt(1-c*c)
    if elbow=='down': s=-s
    q2=math.atan2(s,c)
    q1=(math.atan(py/px) if px else 0.0)-math.atan2(l2*s,l1+l2*c)
    d1=math.degrees(q1)
    d1=(d1+180.0)%360.0-180.0
    return [round(d1,4),round(math.degrees(q2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['general elbow up', [[2, 1, 2, 1, 'up']], [0.0, 90.0]], ['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]]], [['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]]], [['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]]], [['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]]], [['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]], ['upper left down', [[3, 2, -1, 4, 'down']], [131.2512, -70.5288]], ['near full reach', [[5, 3, 7.9, 0.5, 'down']], [10.0523, -17.1893]]]]
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)

prompt:
Targets off the 45 degree diagonal are reached at their mirror image.

Input [l1,l2,px,py,elbow] with positive link lengths. Return [q1,q2] in degrees (4 decimals) for a planar two-link arm whose end effector reaches (px,py); elbow "up" gives q2>=0, "down" gives q2<=0; q1 is wrapped to [-180,180). Points outside the annulus |l1-l2|<=r<=l1+l2 (1e-9 slack on r squared) return "unreachable".

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    l1,l2,px,py,elbow=x
    r2=px*px+py*py
    reach=l1+l2
    if r2>reach*reach+1e-9: return 'unreachable'
    if r2<(l1-l2)**2-1e-9: return 'unreachable'
    c=(r2-l1*l1-l2*l2)/(2*l1*l2)
    c=max(-1.0,min(1.0,c))
    s=math.sqrt(1-c*c)
    if elbow=='down': s=-s
    q2=math.atan2(s,c)
    q1=math.atan2(py,px)-math.atan2(l2*s,l1+l2*c)
    d1=math.degrees(q1)
    d1=d1%360.0
    return [round(d1,4),round(math.degrees(q2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['general elbow up', [[2, 1, 2, 1, 'up']], [0.0, 90.0]], ['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]]], [['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]]], [['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]]], [['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]]], [['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]], ['upper left down', [[3, 2, -1, 4, 'down']], [131.2512, -70.5288]], ['near full reach', [[5, 3, 7.9, 0.5, 'down']], [10.0523, -17.1893]]]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    l1,l2,px,py,elbow=x
    r2=px*px+py*py
    reach=l1+l2
    if r2>reach*reach+1e-9: return 'unreachable'
    if r2<(l1-l2)**2-1e-9: return 'unreachable'
    c=(r2-l1*l1-l2*l2)/(2*l1*l2)
    c=max(-1.0,min(1.0,c))
    s=math.sqrt(1-c*c)
    if elbow=='down': s=-s
    q2=math.atan2(s,c)
    q1=math.atan2(py,px)-math.atan2(l2*s,l1+l2*c)
    d1=math.degrees(q1)
    d1=d1-360.0 if d1>180.0 else d1
    return [round(d1,4),round(math.degrees(q2),4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['general elbow up', [[2, 1, 2, 1, 'up']], [0.0, 90.0]], ['general elbow down', [[2, 1, 2, 1, 'down']], [53.1301, -90.0]], ['fully extended on x axis', [[2, 1, 3, 0, 'up']], [0.0, 0.0]], ['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]]], [['fully extended diagonal', [[3, 4, 4.2, 5.6, 'up']], [53.1301, 0.0]], ['outside workspace', [[2, 1, 3, 1, 'up']], 'unreachable'], ['between reach and reach squared', [[1, 1, 1.9, 1.2, 'up']], 'unreachable'], ['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]]], [['inside inner hole', [[3, 1, 1, 0, 'up']], 'unreachable'], ['inner boundary folded', [[3, 1, 2, 0, 'down']], [0.0, -180.0]], ['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]]], [['second quadrant target', [[2, 1.5, -2, 1, 'down']], [-165.5619, -102.0247]], ['negative y axis', [[1.5, 1, 0, -2, 'up']], [-118.955, 75.5225]], ['behind base elbow down', [[2, 1, -2.5, -0.5, 'down']], [-147.3681, -67.9757]], ['equal links near base', [[1, 1, 0.5, 0.2, 'up']], [-52.5785, 148.7597]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]]], [['third quadrant target', [[2, 2, -2, -1, 'up']], [150.5529, 112.0243]], ['long forearm hole', [[1, 3, 0.5, 1, 'down']], 'unreachable'], ['long forearm reachable', [[1, 3, 2.5, 0.5, 'up']], [-95.7981, 125.6853]], ['positive y axis', [[2, 2, 0, 3, 'down']], [131.4096, -82.8192]], ['wrap past minus 180', [[1, 2, -2.2, -0.3, 'up']], [123.5259, 91.0027]], ['upper left down', [[3, 2, -1, 4, 'down']], [131.2512, -70.5288]], ['near full reach', [[5, 3, 7.9, 0.5, 'down']], [10.0523, -17.1893]]]]
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)

prompt:
Shoulder commands below zero come back as large positive angles.

Input [l1,l2,px,py,elbow] with positive link lengths. Return [q1,q2] in degrees (4 decimals) for a planar two-link arm whose end effector reaches (px,py); elbow "up" gives q2>=0, "down" gives q2<=0; q1 is wrapped to [-180,180). Points outside the annulus |l1-l2|<=r<=l1+l2 (1e-9 slack on r squared) return "unreachable".

split: open-access


prompt:
The tool tip ends 2*l3 past the target along the heading.

Input [l1,l2,l3,px,py,phi,elbow]: planar three-link arm, phi is the required tool heading in degrees. Subtract l3 along phi to get the wrist centre, solve the two-link sub-chain (elbow "up" gives q2>=0), set q3=phi-q1-q2 and wrap each joint to [-180,180) in degrees rounded to 4 places. A wrist centre outside |l1-l2|..l1+l2 returns "unreachable".

split: open-access


prompt:
Headings other than zero produce wild wrist offsets and wrist joint angles.

Input [l1,l2,l3,px,py,phi,elbow]: planar three-link arm, phi is the required tool heading in degrees. Subtract l3 along phi to get the wrist centre, solve the two-link sub-chain (elbow "up" gives q2>=0), set q3=phi-q1-q2 and wrap each joint to [-180,180) in degrees rounded to 4 places. A wrist centre outside |l1-l2|..l1+l2 returns "unreachable".

split: open-access


prompt:
The tool heading is off by exactly the shoulder angle.

Input [l1,l2,l3,px,py,phi,elbow]: planar three-link arm, phi is the required tool heading in degrees. Subtract l3 along phi to get the wrist centre, solve the two-link sub-chain (elbow "up" gives q2>=0), set q3=phi-q1-q2 and wrap each joint to [-180,180) in degrees rounded to 4 places. A wrist centre outside |l1-l2|..l1+l2 returns "unreachable".

split: open-access


prompt:
Multi-turn heading requests yield wrist angles pinned at 180 or -180.

Input [l1,l2,l3,px,py,phi,elbow]: planar three-link arm, phi is the required tool heading in degrees. Subtract l3 along phi to get the wrist centre, solve the two-link sub-chain (elbow "up" gives q2>=0), set q3=phi-q1-q2 and wrap each joint to [-180,180) in degrees rounded to 4 places. A wrist centre outside |l1-l2|..l1+l2 returns "unreachable".

split: open-access


prompt:
The two-link sub-chain is rotated toward the tip, so the wrist lands off the required centre.

Input [l1,l2,l3,px,py,phi,elbow]: planar three-link arm, phi is the required tool heading in degrees. Subtract l3 along phi to get the wrist centre, solve the two-link sub-chain (elbow "up" gives q2>=0), set q3=phi-q1-q2 and wrap each joint to [-180,180) in degrees rounded to 4 places. A wrist centre outside |l1-l2|..l1+l2 returns "unreachable".

split: open-access


prompt:
Targets whose wrist centre is out of reach return a clamped pose instead of "unreachable".

Input [l1,l2,l3,px,py,phi,elbow]: planar three-link arm, phi is the required tool heading in degrees. Subtract l3 along phi to get the wrist centre, solve the two-link sub-chain (elbow "up" gives q2>=0), set q3=phi-q1-q2 and wrap each joint to [-180,180) in degrees rounded to 4 places. A wrist centre outside |l1-l2|..l1+l2 returns "unreachable".

split: open-access


prompt:
Steps move the wrist in a direction that does not reduce the task error whenever q2 is non-zero.

Input [l1,l2,q1,q2,tx,ty,lam,maxstep], angles in degrees, maxstep in radians. Perform one damped least squares step dq = J^T (J J^T + lam^2 I)^-1 e with e = target - fk(q), scale dq so its Euclidean norm is at most maxstep, and return the updated [q1,q2] in degrees rounded to 4 places; a near-singular damped matrix (|det|<1e-12) returns "singular".

split: open-access


prompt:
Steps are over-damped for lambda<1 and under-damped for lambda>1 relative to the contract.

Input [l1,l2,q1,q2,tx,ty,lam,maxstep], angles in degrees, maxstep in radians. Perform one damped least squares step dq = J^T (J J^T + lam^2 I)^-1 e with e = target - fk(q), scale dq so its Euclidean norm is at most maxstep, and return the updated [q1,q2] in degrees rounded to 4 places; a near-singular damped matrix (|det|<1e-12) returns "singular".

split: open-access


prompt:
Every step moves the arm away from the goal.

Input [l1,l2,q1,q2,tx,ty,lam,maxstep], angles in degrees, maxstep in radians. Perform one damped least squares step dq = J^T (J J^T + lam^2 I)^-1 e with e = target - fk(q), scale dq so its Euclidean norm is at most maxstep, and return the updated [q1,q2] in degrees rounded to 4 places; a near-singular damped matrix (|det|<1e-12) returns "singular".

split: open-access


prompt:
Joint deltas are rotated into the wrong space and the arm drifts sideways.

Input [l1,l2,q1,q2,tx,ty,lam,maxstep], angles in degrees, maxstep in radians. Perform one damped least squares step dq = J^T (J J^T + lam^2 I)^-1 e with e = target - fk(q), scale dq so its Euclidean norm is at most maxstep, and return the updated [q1,q2] in degrees rounded to 4 places; a near-singular damped matrix (|det|<1e-12) returns "singular".

split: open-access


prompt:
Large corrections change direction instead of being scaled along the DLS direction.

Input [l1,l2,q1,q2,tx,ty,lam,maxstep], angles in degrees, maxstep in radians. Perform one damped least squares step dq = J^T (J J^T + lam^2 I)^-1 e with e = target - fk(q), scale dq so its Euclidean norm is at most maxstep, and return the updated [q1,q2] in degrees rounded to 4 places; a near-singular damped matrix (|det|<1e-12) returns "singular".

split: open-access


prompt:
Updates are 57 times too small relative to the degree-valued joints.

Input [l1,l2,q1,q2,tx,ty,lam,maxstep], angles in degrees, maxstep in radians. Perform one damped least squares step dq = J^T (J J^T + lam^2 I)^-1 e with e = target - fk(q), scale dq so its Euclidean norm is at most maxstep, and return the updated [q1,q2] in degrees rounded to 4 places; a near-singular damped matrix (|det|<1e-12) returns "singular".

split: open-access


prompt:
Joints nearer the base rotate toward a tip position that no longer exists, overshooting the target.

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)].

split: open-access


prompt:
Converged configurations differ from the tip-first CCD contract and use more sweeps.

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)].

split: open-access


prompt:
Each joint rotates away from the target by the angle it should close.

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)].

split: open-access


prompt:
Joints drift beyond their limits over repeated sweeps.

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)].

split: open-access


prompt:
Solves that start inside tolerance report one sweep instead of zero, and every count is one high after convergence.

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)].

split: open-access


prompt:
Joint limits are effectively disabled because 45 is treated as 45 radians.

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)].

split: open-access


prompt:
Reachable targets are stretched toward as unreachable, and far targets near the tip enter the iteration.

Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.

split: open-access


prompt:
The returned chain has its base displaced from the fixed mount.

Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.

split: open-access


prompt:
Joints are placed at the complementary fraction of the segment and link lengths are not preserved.

Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.

split: open-access


prompt:
Chains with unequal links come back with swapped segment lengths.

Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.

split: open-access


prompt:
Stretched chains for unreachable targets have compressed outer links.

Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.

split: open-access


prompt:
A solve that converges on its final allowed pass is reported as "max-iterations".

Input [points, target, tol, iters]: planar joint positions from base to tip. Link lengths come from the initial points. If the base-to-target distance exceeds the total length, stretch every link toward the target and return ["unreachable", points]. Otherwise run FABRIK passes (tip to target and backward, base re-anchored, forward) while the tip is farther than tol and fewer than iters passes ran; return [status, passes, points] with status "reached" when the final tip error <= tol else "max-iterations"; coordinates rounded to 4 places.

split: open-access


prompt:
A solution sitting on a limit is discarded and a costlier branch or "none" is returned.

Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".

split: open-access


prompt:
A branch that would require driving through a hard stop looks cheap and is selected.

Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".

split: open-access


prompt:
A candidate 10 degrees away across the +/-180 seam costs as if it were 350 degrees away.

Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".

split: open-access


prompt:
Moving a heavy base joint is charged the same as moving a light wrist joint.

Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".

split: open-access


prompt:
Among symmetric solutions the last one listed is chosen, flipping the arm unexpectedly.

Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".

split: open-access


prompt:
When an infeasible candidate precedes the winner, the reported index points at the wrong solution.

Input [candidates, current, limits, weights] with joint angles in degrees. A limits entry is null for a continuous joint (distance is the shortest wrap-around difference) or [lo,hi] for a limited joint (inclusive bounds, distance is the plain difference because motion cannot pass the stop). Cost is sum w*d^2. Return [index into candidates, cost rounded 4] for the cheapest feasible candidate, earliest wins ties within 1e-9, or "none".

split: open-access


prompt:
Reachable heights are rejected and heights above the home plane are accepted.

Input [l1,l2,d0,stroke,px,py,pz,yaw,arm]. The quill extends downward: d3=d0-pz must lie in [0,stroke] else "out-of-stroke" (checked first). Planar reach uses cos t2=(px^2+py^2-l1^2-l2^2)/(2 l1 l2); |cos|>1 returns "unreachable". arm "right" takes t2<=0. Tool yaw (degrees) equals t1+t2+t4. Return [t1,t2,d3,t4] with angles in degrees wrapped to (-180,180], all rounded to 4 places.

split: open-access


prompt:
Targets at full retraction or full extension report "out-of-stroke".

Input [l1,l2,d0,stroke,px,py,pz,yaw,arm]. The quill extends downward: d3=d0-pz must lie in [0,stroke] else "out-of-stroke" (checked first). Planar reach uses cos t2=(px^2+py^2-l1^2-l2^2)/(2 l1 l2); |cos|>1 returns "unreachable". arm "right" takes t2<=0. Tool yaw (degrees) equals t1+t2+t4. Return [t1,t2,d3,t4] with angles in degrees wrapped to (-180,180], all rounded to 4 places.

split: open-access


prompt:
The tool heading is off by the sum of the shoulder and elbow angles.

Input [l1,l2,d0,stroke,px,py,pz,yaw,arm]. The quill extends downward: d3=d0-pz must lie in [0,stroke] else "out-of-stroke" (checked first). Planar reach uses cos t2=(px^2+py^2-l1^2-l2^2)/(2 l1 l2); |cos|>1 returns "unreachable". arm "right" takes t2<=0. Tool yaw (degrees) equals t1+t2+t4. Return [t1,t2,d3,t4] with angles in degrees wrapped to (-180,180], all rounded to 4 places.

split: open-access


prompt:
Yaw requests of several turns leave the roll joint outside (-180,180].

Input [l1,l2,d0,stroke,px,py,pz,yaw,arm]. The quill extends downward: d3=d0-pz must lie in [0,stroke] else "out-of-stroke" (checked first). Planar reach uses cos t2=(px^2+py^2-l1^2-l2^2)/(2 l1 l2); |cos|>1 returns "unreachable". arm "right" takes t2<=0. Tool yaw (degrees) equals t1+t2+t4. Return [t1,t2,d3,t4] with angles in degrees wrapped to (-180,180], all rounded to 4 places.

split: open-access


prompt:
Elbow angles are wrong everywhere except at unit radius.

Input [l1,l2,d0,stroke,px,py,pz,yaw,arm]. The quill extends downward: d3=d0-pz must lie in [0,stroke] else "out-of-stroke" (checked first). Planar reach uses cos t2=(px^2+py^2-l1^2-l2^2)/(2 l1 l2); |cos|>1 returns "unreachable". arm "right" takes t2<=0. Tool yaw (degrees) equals t1+t2+t4. Return [t1,t2,d3,t4] with angles in degrees wrapped to (-180,180], all rounded to 4 places.

split: open-access


prompt:
A target that is both too high and too far reports "unreachable" instead of "out-of-stroke".

Input [l1,l2,d0,stroke,px,py,pz,yaw,arm]. The quill extends downward: d3=d0-pz must lie in [0,stroke] else "out-of-stroke" (checked first). Planar reach uses cos t2=(px^2+py^2-l1^2-l2^2)/(2 l1 l2); |cos|>1 returns "unreachable". arm "right" takes t2<=0. Tool yaw (degrees) equals t1+t2+t4. Return [t1,t2,d3,t4] with angles in degrees wrapped to (-180,180], all rounded to 4 places.

split: open-access


prompt:
Nearly aligned wrist axes produce huge, noise-driven a and g values instead of the documented convention.

Input [R, flip]: R is a 3x3 wrist rotation R = Rz(a) Ry(b) Rz(g). Return [a,b,g] in degrees rounded to 4. With flip false b is in [0,180]; with flip true b is negative and a,g are shifted by 180 degrees. When hypot(R02,R12) < 1e-9 the wrist is singular: a=0 and b=0 (R22>0, g=atan2(R10,R00)) or b=180 (g=atan2(R01,-R00)).

split: open-access


prompt:
At b=180 the returned g has the wrong sign relation to the rotation.

Input [R, flip]: R is a 3x3 wrist rotation R = Rz(a) Ry(b) Rz(g). Return [a,b,g] in degrees rounded to 4. With flip false b is in [0,180]; with flip true b is negative and a,g are shifted by 180 degrees. When hypot(R02,R12) < 1e-9 the wrist is singular: a=0 and b=0 (R22>0, g=atan2(R10,R00)) or b=180 (g=atan2(R01,-R00)).

split: open-access


prompt:
The flip solution reproduces a different orientation from the input matrix.

Input [R, flip]: R is a 3x3 wrist rotation R = Rz(a) Ry(b) Rz(g). Return [a,b,g] in degrees rounded to 4. With flip false b is in [0,180]; with flip true b is negative and a,g are shifted by 180 degrees. When hypot(R02,R12) < 1e-9 the wrist is singular: a=0 and b=0 (R22>0, g=atan2(R10,R00)) or b=180 (g=atan2(R01,-R00)).

split: open-access


prompt:
The final roll angle is mirrored to 180-g.

Input [R, flip]: R is a 3x3 wrist rotation R = Rz(a) Ry(b) Rz(g). Return [a,b,g] in degrees rounded to 4. With flip false b is in [0,180]; with flip true b is negative and a,g are shifted by 180 degrees. When hypot(R02,R12) < 1e-9 the wrist is singular: a=0 and b=0 (R22>0, g=atan2(R10,R00)) or b=180 (g=atan2(R01,-R00)).

split: open-access


prompt:
Tilts beyond 90 degrees fold back, giving the supplementary pitch.

Input [R, flip]: R is a 3x3 wrist rotation R = Rz(a) Ry(b) Rz(g). Return [a,b,g] in degrees rounded to 4. With flip false b is in [0,180]; with flip true b is negative and a,g are shifted by 180 degrees. When hypot(R02,R12) < 1e-9 the wrist is singular: a=0 and b=0 (R22>0, g=atan2(R10,R00)) or b=180 (g=atan2(R01,-R00)).

split: open-access


prompt:
The first angle equals a function of the last angle, so a and g appear swapped.

Input [R, flip]: R is a 3x3 wrist rotation R = Rz(a) Ry(b) Rz(g). Return [a,b,g] in degrees rounded to 4. With flip false b is in [0,180]; with flip true b is negative and a,g are shifted by 180 degrees. When hypot(R02,R12) < 1e-9 the wrist is singular: a=0 and b=0 (R22>0, g=atan2(R10,R00)) or b=180 (g=atan2(R01,-R00)).

split: open-access


prompt:
Every carriage height is computed for towers that are 2e too far out.

Input [R,e,L,x,y,z,hmax]: linear delta printer, towers at 90, 210 and 330 degrees (in that output order) on radius R, effector joints offset e toward the centre so the effective radius is R-e, diagonal rods of length L. Carriage height is z+sqrt(L^2-dx^2-dy^2); a negative radicand returns "unreachable", a carriage above hmax returns "out-of-travel". Return heights rounded to 4.

split: open-access


prompt:
Carriage heights belong to a rotated tower layout, so moves in y tilt the effector.

Input [R,e,L,x,y,z,hmax]: linear delta printer, towers at 90, 210 and 330 degrees (in that output order) on radius R, effector joints offset e toward the centre so the effective radius is R-e, diagonal rods of length L. Carriage height is z+sqrt(L^2-dx^2-dy^2); a negative radicand returns "unreachable", a carriage above hmax returns "out-of-travel". Return heights rounded to 4.

split: open-access


prompt:
A reachable point on the rod-length boundary returns "unreachable".

Input [R,e,L,x,y,z,hmax]: linear delta printer, towers at 90, 210 and 330 degrees (in that output order) on radius R, effector joints offset e toward the centre so the effective radius is R-e, diagonal rods of length L. Carriage height is z+sqrt(L^2-dx^2-dy^2); a negative radicand returns "unreachable", a carriage above hmax returns "out-of-travel". Return heights rounded to 4.

split: open-access


prompt:
Carriages are placed below the effector plane.

Input [R,e,L,x,y,z,hmax]: linear delta printer, towers at 90, 210 and 330 degrees (in that output order) on radius R, effector joints offset e toward the centre so the effective radius is R-e, diagonal rods of length L. Carriage height is z+sqrt(L^2-dx^2-dy^2); a negative radicand returns "unreachable", a carriage above hmax returns "out-of-travel". Return heights rounded to 4.

split: open-access


prompt:
Moves whose carriages exceed the top are accepted.

Input [R,e,L,x,y,z,hmax]: linear delta printer, towers at 90, 210 and 330 degrees (in that output order) on radius R, effector joints offset e toward the centre so the effective radius is R-e, diagonal rods of length L. Carriage height is z+sqrt(L^2-dx^2-dy^2); a negative radicand returns "unreachable", a carriage above hmax returns "out-of-travel". Return heights rounded to 4.

split: open-access


prompt:
Diagonal offsets produce wrong carriage heights while axis-aligned ones look correct.

Input [R,e,L,x,y,z,hmax]: linear delta printer, towers at 90, 210 and 330 degrees (in that output order) on radius R, effector joints offset e toward the centre so the effective radius is R-e, diagonal rods of length L. Carriage height is z+sqrt(L^2-dx^2-dy^2); a negative radicand returns "unreachable", a carriage above hmax returns "out-of-travel". Return heights rounded to 4.

split: open-access


prompt:
Poses with more than one non-zero attitude angle produce wrong leg lengths.

Input [A,B,pose,lmin,lmax]: base anchors A and platform anchors B (platform frame), pose [x,y,z,roll,pitch,yaw] in degrees with R = Rz(yaw) Ry(pitch) Rx(roll). Leg i length is |p + R b_i - a_i|. Return lengths rounded to 4, or ["limit", i, length] for the first leg outside the inclusive range [lmin,lmax].

split: open-access


prompt:
Positive pitch tilts the platform the wrong way.

Input [A,B,pose,lmin,lmax]: base anchors A and platform anchors B (platform frame), pose [x,y,z,roll,pitch,yaw] in degrees with R = Rz(yaw) Ry(pitch) Rx(roll). Leg i length is |p + R b_i - a_i|. Return lengths rounded to 4, or ["limit", i, length] for the first leg outside the inclusive range [lmin,lmax].

split: open-access


prompt:
Legs are computed for the platform rotated the opposite way.

Input [A,B,pose,lmin,lmax]: base anchors A and platform anchors B (platform frame), pose [x,y,z,roll,pitch,yaw] in degrees with R = Rz(yaw) Ry(pitch) Rx(roll). Leg i length is |p + R b_i - a_i|. Return lengths rounded to 4, or ["limit", i, length] for the first leg outside the inclusive range [lmin,lmax].

split: open-access


prompt:
A pose whose legs sit exactly at lmin or lmax is reported as a limit violation.

Input [A,B,pose,lmin,lmax]: base anchors A and platform anchors B (platform frame), pose [x,y,z,roll,pitch,yaw] in degrees with R = Rz(yaw) Ry(pitch) Rx(roll). Leg i length is |p + R b_i - a_i|. Return lengths rounded to 4, or ["limit", i, length] for the first leg outside the inclusive range [lmin,lmax].

split: open-access


prompt:
When several legs are out of range the report points at the wrong actuator.

Input [A,B,pose,lmin,lmax]: base anchors A and platform anchors B (platform frame), pose [x,y,z,roll,pitch,yaw] in degrees with R = Rz(yaw) Ry(pitch) Rx(roll). Leg i length is |p + R b_i - a_i|. Return lengths rounded to 4, or ["limit", i, length] for the first leg outside the inclusive range [lmin,lmax].

split: open-access


prompt:
Roll and yaw commands are exchanged.

Input [A,B,pose,lmin,lmax]: base anchors A and platform anchors B (platform frame), pose [x,y,z,roll,pitch,yaw] in degrees with R = Rz(yaw) Ry(pitch) Rx(roll). Leg i length is |p + R b_i - a_i|. Return lengths rounded to 4, or ["limit", i, length] for the first leg outside the inclusive range [lmin,lmax].

split: open-access


prompt:
Feet land a coxa length short of the commanded position.

Input [coxa,femur,tibia,x,y,z,fmin,fmax]: foot target in the leg root frame (z up). Coxa yaw is atan2(y,x); the femur/tibia pair works in the vertical plane at horizontal distance hypot(x,y)-coxa. With D the distance from the femur pivot to the foot, D>femur+tibia or D<|femur-tibia| or D==0 is "unreachable". Femur pitch is atan2(z,h) plus the triangle angle at the femur pivot (knee up); tibia angle is the interior knee angle minus 180 (0 = straight). Femur pitch outside [fmin,fmax] returns "limit". Degrees rounded to 4.

split: open-access


prompt:
The knee bends below the femur-foot line, dragging the tibia through the ground.

Input [coxa,femur,tibia,x,y,z,fmin,fmax]: foot target in the leg root frame (z up). Coxa yaw is atan2(y,x); the femur/tibia pair works in the vertical plane at horizontal distance hypot(x,y)-coxa. With D the distance from the femur pivot to the foot, D>femur+tibia or D<|femur-tibia| or D==0 is "unreachable". Femur pitch is atan2(z,h) plus the triangle angle at the femur pivot (knee up); tibia angle is the interior knee angle minus 180 (0 = straight). Femur pitch outside [fmin,fmax] returns "limit". Degrees rounded to 4.

split: open-access


prompt:
A straight leg reports 180 instead of 0 and bent legs are off by 180.

Input [coxa,femur,tibia,x,y,z,fmin,fmax]: foot target in the leg root frame (z up). Coxa yaw is atan2(y,x); the femur/tibia pair works in the vertical plane at horizontal distance hypot(x,y)-coxa. With D the distance from the femur pivot to the foot, D>femur+tibia or D<|femur-tibia| or D==0 is "unreachable". Femur pitch is atan2(z,h) plus the triangle angle at the femur pivot (knee up); tibia angle is the interior knee angle minus 180 (0 = straight). Femur pitch outside [fmin,fmax] returns "limit". Degrees rounded to 4.

split: open-access


prompt:
Femur pitch is wrong whenever femur and tibia differ in length.

Input [coxa,femur,tibia,x,y,z,fmin,fmax]: foot target in the leg root frame (z up). Coxa yaw is atan2(y,x); the femur/tibia pair works in the vertical plane at horizontal distance hypot(x,y)-coxa. With D the distance from the femur pivot to the foot, D>femur+tibia or D<|femur-tibia| or D==0 is "unreachable". Femur pitch is atan2(z,h) plus the triangle angle at the femur pivot (knee up); tibia angle is the interior knee angle minus 180 (0 = straight). Femur pitch outside [fmin,fmax] returns "limit". Degrees rounded to 4.

split: open-access


prompt:
Pitch limits almost never trigger, allowing poses beyond the servo range.

Input [coxa,femur,tibia,x,y,z,fmin,fmax]: foot target in the leg root frame (z up). Coxa yaw is atan2(y,x); the femur/tibia pair works in the vertical plane at horizontal distance hypot(x,y)-coxa. With D the distance from the femur pivot to the foot, D>femur+tibia or D<|femur-tibia| or D==0 is "unreachable". Femur pitch is atan2(z,h) plus the triangle angle at the femur pivot (knee up); tibia angle is the interior knee angle minus 180 (0 = straight). Femur pitch outside [fmin,fmax] returns "limit". Degrees rounded to 4.

split: open-access


prompt:
Feet far below the body are accepted and then solved with a clamped cosine.

Input [coxa,femur,tibia,x,y,z,fmin,fmax]: foot target in the leg root frame (z up). Coxa yaw is atan2(y,x); the femur/tibia pair works in the vertical plane at horizontal distance hypot(x,y)-coxa. With D the distance from the femur pivot to the foot, D>femur+tibia or D<|femur-tibia| or D==0 is "unreachable". Femur pitch is atan2(z,h) plus the triangle angle at the femur pivot (knee up); tibia angle is the interior knee angle minus 180 (0 = straight). Femur pitch outside [fmin,fmax] returns "limit". Degrees rounded to 4.

split: open-access


prompt:
Joint velocities are computed for the wrong lever arms and the task error is not reduced.

Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.

split: open-access


prompt:
The joint-centering objective disturbs the end effector instead of moving in the self-motion manifold.

Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.

split: open-access


prompt:
Self-motion increases the distance to the preferred configuration.

Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.

split: open-access


prompt:
Task steps are skewed whenever the Jacobian rows are correlated.

Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.

split: open-access


prompt:
The task error is computed from the wrong tip position.

Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.

split: open-access


prompt:
Joint centering moves the end effector off the task.

Input [lengths(3), q(3 deg), target, qc(3 deg), k]: planar 3R arm, position task only. One step dq = J+ e + (I - J+ J) z, J+ = J^T (J J^T)^-1, e = target - fk(q), z = k*(qc-q) in radians. Return the updated q in degrees rounded to 4, or "singular" when |det(J J^T)|<1e-12.

split: open-access


prompt:
Damping switches on and off as the shoulder rotates, although conditioning depends only on the elbow.

Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".

split: open-access


prompt:
Every elbow-down pose is damped as if it sat on the singularity.

Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".

split: open-access


prompt:
Joint velocities near the zone edge are over-damped.

Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".

split: open-access


prompt:
With zero maximum damping, steps inside the zone are labeled "damped" although they are exact.

Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".

split: open-access


prompt:
A straight arm returns "singular" even though damping makes the system solvable.

Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".

split: open-access


prompt:
Well-conditioned poses get damped because (1-w/w0)^2 is positive for w>w0.

Input [l1,l2,q1,q2,vx,vy,w0,lam0]: resolved-rate step for a planar 2R arm. Manipulability w=|l1 l2 sin q2|. Damping lam^2 = 0 when w>=w0, else lam0^2 (1-w/w0)^2. Solve (J^T J + lam^2 I) qdot = J^T v; if that matrix determinant is below 1e-12 return ["singular", w]. Return [status, qdot1, qdot2] in degrees per second, status "damped" when lam^2>0 else "exact".

split: open-access


prompt:
After an unreachable waypoint a branch change is no longer counted as a flip.

Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].

split: open-access


prompt:
Near the +/-180 seam the solver picks the far branch or reports a spurious jump.

Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].

split: open-access


prompt:
Waypoints are rejected as jumps even though the other branch is close.

Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].

split: open-access


prompt:
Every trajectory reports one extra flip.

Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].

split: open-access


prompt:
A continuous joint command steps from 179 to -179 instead of moving to 181.

Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].

split: open-access


prompt:
After a rejected waypoint the next target is judged against a pose the arm never reached.

Input [l1,l2,q0,targets,maxjump]: planar 2R arm with continuous joints following a list of targets from joint state q0 (degrees). For each target compute the elbow +/- solutions; unreachable targets append "hold". Choose the solution with the smallest max wrapped joint change from the previous state; if that change exceeds maxjump append "jump" and keep the state. Otherwise unwrap the solution to lie within 180 of the previous angles, append it (3 decimals) and count a flip whenever the elbow sign differs from the last accepted one. Return [outputs, flips].

split: open-access


prompt:
Every solution is computed for a target shifted up by d1.

Input [d1,a2,a3,px,py,pz,reach,elbow]: base yaw t1, shoulder t2, elbow t3 with shoulder height d1. reach "front" uses t1=atan2(py,px) and r=hypot(px,py); "back" turns t1 by 180 degrees and uses -r. With s=pz-d1, cos t3=(r^2+s^2-a2^2-a3^2)/(2 a2 a3); beyond +/-(1+1e-12) is "unreachable", else clamp. elbow "up" takes sin t3<=0. t2=atan2(s,r)-atan2(a3 sin t3, a2+a3 cos t3). Angles in degrees wrapped to [-180,180), rounded 4.

split: open-access


prompt:
The negated radius sends the arm behind a base that was never turned around.

Input [d1,a2,a3,px,py,pz,reach,elbow]: base yaw t1, shoulder t2, elbow t3 with shoulder height d1. reach "front" uses t1=atan2(py,px) and r=hypot(px,py); "back" turns t1 by 180 degrees and uses -r. With s=pz-d1, cos t3=(r^2+s^2-a2^2-a3^2)/(2 a2 a3); beyond +/-(1+1e-12) is "unreachable", else clamp. elbow "up" takes sin t3<=0. t2=atan2(s,r)-atan2(a3 sin t3, a2+a3 cos t3). Angles in degrees wrapped to [-180,180), rounded 4.

split: open-access


prompt:
Targets above or below the shoulder get the elbow angle of a target in the shoulder plane.

Input [d1,a2,a3,px,py,pz,reach,elbow]: base yaw t1, shoulder t2, elbow t3 with shoulder height d1. reach "front" uses t1=atan2(py,px) and r=hypot(px,py); "back" turns t1 by 180 degrees and uses -r. With s=pz-d1, cos t3=(r^2+s^2-a2^2-a3^2)/(2 a2 a3); beyond +/-(1+1e-12) is "unreachable", else clamp. elbow "up" takes sin t3<=0. t2=atan2(s,r)-atan2(a3 sin t3, a2+a3 cos t3). Angles in degrees wrapped to [-180,180), rounded 4.

split: open-access


prompt:
Over-the-back solutions come out mirrored in front of the robot.

Input [d1,a2,a3,px,py,pz,reach,elbow]: base yaw t1, shoulder t2, elbow t3 with shoulder height d1. reach "front" uses t1=atan2(py,px) and r=hypot(px,py); "back" turns t1 by 180 degrees and uses -r. With s=pz-d1, cos t3=(r^2+s^2-a2^2-a3^2)/(2 a2 a3); beyond +/-(1+1e-12) is "unreachable", else clamp. elbow "up" takes sin t3<=0. t2=atan2(s,r)-atan2(a3 sin t3, a2+a3 cos t3). Angles in degrees wrapped to [-180,180), rounded 4.

split: open-access


prompt:
A base yaw of exactly 180 is reported as +180 instead of -180.

Input [d1,a2,a3,px,py,pz,reach,elbow]: base yaw t1, shoulder t2, elbow t3 with shoulder height d1. reach "front" uses t1=atan2(py,px) and r=hypot(px,py); "back" turns t1 by 180 degrees and uses -r. With s=pz-d1, cos t3=(r^2+s^2-a2^2-a3^2)/(2 a2 a3); beyond +/-(1+1e-12) is "unreachable", else clamp. elbow "up" takes sin t3<=0. t2=atan2(s,r)-atan2(a3 sin t3, a2+a3 cos t3). Angles in degrees wrapped to [-180,180), rounded 4.

split: open-access


prompt:
Targets exactly at full extension computed through hypot report "unreachable".

Input [d1,a2,a3,px,py,pz,reach,elbow]: base yaw t1, shoulder t2, elbow t3 with shoulder height d1. reach "front" uses t1=atan2(py,px) and r=hypot(px,py); "back" turns t1 by 180 degrees and uses -r. With s=pz-d1, cos t3=(r^2+s^2-a2^2-a3^2)/(2 a2 a3); beyond +/-(1+1e-12) is "unreachable", else clamp. elbow "up" takes sin t3<=0. t2=atan2(s,r)-atan2(a3 sin t3, a2+a3 cos t3). Angles in degrees wrapped to [-180,180), rounded 4.

split: open-access


prompt:
Every joint is commanded 2*offset away from the intended angle.

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].

split: open-access


prompt:
Motor commands are ratio^2 too small.

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].

split: open-access


prompt:
Exact half counts round to even, so +0.5 counts sometimes round down.

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].

split: open-access


prompt:
Reversed joints get the zero offset applied with the wrong sign.

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].

split: open-access


prompt:
Commands beyond encoder travel pass because the geometric angle is small.

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].

split: open-access


prompt:
Counts are four times too small.

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].

split: open-access


prompt:
The step length is tuned to a vector that is not the predicted task-space motion.

Input [l1,l2,q1,q2,tx,ty]: one Jacobian-transpose step for a planar 2R arm with the error-optimal gain alpha = <e, J J^T e> / <J J^T e, J J^T e>, dq = alpha J^T e. When <J J^T e, J J^T e> < 1e-15 return ["stationary", q1, q2]. Otherwise return ["step", alpha (6 decimals), q1 and q2 updated in degrees (4 decimals)].

split: open-access


prompt:
The step gain ignores how well the predicted motion aligns with the error.

Input [l1,l2,q1,q2,tx,ty]: one Jacobian-transpose step for a planar 2R arm with the error-optimal gain alpha = <e, J J^T e> / <J J^T e, J J^T e>, dq = alpha J^T e. When <J J^T e, J J^T e> < 1e-15 return ["stationary", q1, q2]. Otherwise return ["step", alpha (6 decimals), q1 and q2 updated in degrees (4 decimals)].

split: open-access


prompt:
Steps are too long or too short depending on the magnitude of J J^T e.

Input [l1,l2,q1,q2,tx,ty]: one Jacobian-transpose step for a planar 2R arm with the error-optimal gain alpha = <e, J J^T e> / <J J^T e, J J^T e>, dq = alpha J^T e. When <J J^T e, J J^T e> < 1e-15 return ["stationary", q1, q2]. Otherwise return ["step", alpha (6 decimals), q1 and q2 updated in degrees (4 decimals)].

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    edges={}
    for f in faces:
        n=len(f)
        for i in range(n):
            a,b=f[i],f[(i+1)%n]
            k=(a,b)
            edges[k]=edges.get(k,0)+1
    verts=sorted({v for f in faces for v in f})
    V=len(verts)
    E=len(edges)
    Fc=len(faces)
    chi=V-E+Fc
    parent={v:v for v in verts}
    def find(v):
        while parent[v]!=v:
            v=parent[v]
        return v
    for a,b in edges:
        ra,rb=find(a),find(b)
        if ra!=rb: parent[ra]=rb
    comps=len({find(v) for v in verts})
    bpar={}
    def bfind(v):
        while bpar[v]!=v:
            v=bpar[v]
        return v
    for (a,b),c in edges.items():
        if c==1:
            bpar.setdefault(a,a)
            bpar.setdefault(b,b)
            ra,rb=bfind(a),bfind(b)
            if ra!=rb: bpar[ra]=rb
    loops=len({bfind(v) for v in bpar})
    g=2*comps-chi-loops
    genus=None if g%2 else g//2
    return [V,E,Fc,chi,comps,loops,genus]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]]], [['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]]], [['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]], ['gapped indices', [[[0, 1, 5], [1, 7, 5]]], [4, 5, 2, 1, 1, 1, 0]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]]], [['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]]], [['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]], ['tetrahedra sharing a face', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [0, 1, 4], [1, 2, 4], [0, 4, 2]]], [5, 9, 7, 3, 1, 0, None]]]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    edges={}
    for f in faces:
        n=len(f)
        for i in range(n):
            a,b=f[i],f[(i+1)%n]
            k=(min(a,b),max(a,b)) if n==3 else (a,b)
            edges[k]=edges.get(k,0)+1
    verts=sorted({v for f in faces for v in f})
    V=len(verts)
    E=len(edges)
    Fc=len(faces)
    chi=V-E+Fc
    parent={v:v for v in verts}
    def find(v):
        while parent[v]!=v:
            v=parent[v]
        return v
    for a,b in edges:
        ra,rb=find(a),find(b)
        if ra!=rb: parent[ra]=rb
    comps=len({find(v) for v in verts})
    bpar={}
    def bfind(v):
        while bpar[v]!=v:
            v=bpar[v]
        return v
    for (a,b),c in edges.items():
        if c==1:
            bpar.setdefault(a,a)
            bpar.setdefault(b,b)
            ra,rb=bfind(a),bfind(b)
            if ra!=rb: bpar[ra]=rb
    loops=len({bfind(v) for v in bpar})
    g=2*comps-chi-loops
    genus=None if g%2 else g//2
    return [V,E,Fc,chi,comps,loops,genus]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]]], [['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]]], [['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]], ['gapped indices', [[[0, 1, 5], [1, 7, 5]]], [4, 5, 2, 1, 1, 1, 0]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]]], [['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]]], [['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]], ['tetrahedra sharing a face', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [0, 1, 4], [1, 2, 4], [0, 4, 2]]], [5, 9, 7, 3, 1, 0, None]]]]
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)

prompt:
Closed meshes report twice as many edges and a strongly negative Euler characteristic.

Input: list of polygon faces (vertex index lists). V counts referenced vertices, E distinct undirected edges (closing edge included), F faces, chi=V-E+F, C connected components (via edges), B boundary loops = components of the graph of edges used by exactly one face. Return [V,E,F,chi,C,B,genus] with genus=(2C-chi-B)/2 or null when that numerator is odd (non-orientable signal).

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    edges={}
    for f in faces:
        n=len(f)
        for i in range(n-1):
            a,b=f[i],f[i+1]
            k=(min(a,b),max(a,b))
            edges[k]=edges.get(k,0)+1
    verts=sorted({v for f in faces for v in f})
    V=len(verts)
    E=len(edges)
    Fc=len(faces)
    chi=V-E+Fc
    parent={v:v for v in verts}
    def find(v):
        while parent[v]!=v:
            v=parent[v]
        return v
    for a,b in edges:
        ra,rb=find(a),find(b)
        if ra!=rb: parent[ra]=rb
    comps=len({find(v) for v in verts})
    bpar={}
    def bfind(v):
        while bpar[v]!=v:
            v=bpar[v]
        return v
    for (a,b),c in edges.items():
        if c==1:
            bpar.setdefault(a,a)
            bpar.setdefault(b,b)
            ra,rb=bfind(a),bfind(b)
            if ra!=rb: bpar[ra]=rb
    loops=len({bfind(v) for v in bpar})
    g=2*comps-chi-loops
    genus=None if g%2 else g//2
    return [V,E,Fc,chi,comps,loops,genus]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]]], [['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]]], [['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]], ['gapped indices', [[[0, 1, 5], [1, 7, 5]]], [4, 5, 2, 1, 1, 1, 0]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]]], [['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['gapped indices', [[[0, 1, 5], [1, 7, 5]]], [4, 5, 2, 1, 1, 1, 0]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]]], [['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]], ['tetrahedra sharing a face', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [0, 1, 4], [1, 2, 4], [0, 4, 2]]], [5, 9, 7, 3, 1, 0, None]]]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    edges={}
    for f in faces:
        n=len(f)
        for i in range(n):
            a,b=f[i],f[min(i+1,n-1)]
            k=(min(a,b),max(a,b))
            edges[k]=edges.get(k,0)+1
    verts=sorted({v for f in faces for v in f})
    V=len(verts)
    E=len(edges)
    Fc=len(faces)
    chi=V-E+Fc
    parent={v:v for v in verts}
    def find(v):
        while parent[v]!=v:
            v=parent[v]
        return v
    for a,b in edges:
        ra,rb=find(a),find(b)
        if ra!=rb: parent[ra]=rb
    comps=len({find(v) for v in verts})
    bpar={}
    def bfind(v):
        while bpar[v]!=v:
            v=bpar[v]
        return v
    for (a,b),c in edges.items():
        if c==1:
            bpar.setdefault(a,a)
            bpar.setdefault(b,b)
            ra,rb=bfind(a),bfind(b)
            if ra!=rb: bpar[ra]=rb
    loops=len({bfind(v) for v in bpar})
    g=2*comps-chi-loops
    genus=None if g%2 else g//2
    return [V,E,Fc,chi,comps,loops,genus]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]]], [['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['disk fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [6, 10, 5, 1, 1, 1, 0]], ['quad annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [6, 9, 3, 0, 1, 2, 0]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]]], [['octahedron', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]]], [6, 12, 8, 2, 1, 0, 0]], ['mobius strip', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 0, 3]]], [6, 9, 3, 0, 1, 1, None]], ['projective plane', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [1, 2, 4], [2, 3, 5], [3, 4, 1], [4, 5, 2], [5, 1, 3]]], [6, 15, 10, 1, 1, 0, None]], ['two tetrahedra', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [4, 6, 5], [4, 5, 7], [5, 6, 7], [4, 7, 6]]], [8, 12, 8, 4, 2, 0, 0]], ['gapped indices', [[[0, 1, 5], [1, 7, 5]]], [4, 5, 2, 1, 1, 1, 0]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]]], [['quad torus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0], [3, 6, 7, 4], [4, 7, 8, 5], [5, 8, 6, 3], [6, 0, 1, 7], [7, 1, 2, 8], [8, 2, 0, 6]]], [9, 18, 9, 0, 1, 0, 1]], ['gapped indices', [[[0, 1, 5], [1, 7, 5]]], [4, 5, 2, 1, 1, 1, 0]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [8, 16, 8, 0, 1, 2, 0]], ['book of three pages', [[[0, 1, 2], [1, 0, 3], [0, 1, 4]]], [5, 7, 3, 1, 1, 1, 0]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]]], [['tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], [4, 6, 4, 2, 1, 0, 0]], ['cube quads', [[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [8, 12, 6, 2, 1, 0, 0]], ['triangle torus', [[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]]], [9, 27, 18, 0, 1, 0, 1]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [9, 12, 4, 1, 1, 1, 0]], ['mixed quad and triangle', [[[0, 1, 2, 3], [3, 2, 4]]], [5, 6, 2, 1, 1, 1, 0]], ['tetra plus disk', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [10, 11, 12], [10, 12, 13], [10, 13, 14], [10, 14, 15], [10, 15, 11]]], [10, 16, 9, 3, 2, 1, 0]], ['tetrahedra sharing a face', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2], [0, 1, 4], [1, 2, 4], [0, 4, 2]]], [5, 9, 7, 3, 1, 0, None]]]]
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)

prompt:
Every face loses one edge, so E is too small and chi too large.

Input: list of polygon faces (vertex index lists). V counts referenced vertices, E distinct undirected edges (closing edge included), F faces, chi=V-E+F, C connected components (via edges), B boundary loops = components of the graph of edges used by exactly one face. Return [V,E,F,chi,C,B,genus] with genus=(2C-chi-B)/2 or null when that numerator is odd (non-orientable signal).

split: open-access