FA-93526 / Solar tracker geometry / Open access
Tracker actuator step command: slew limit direction · case 01
Large eastward corrections are executed in one jump.
ROOT CAUSE
The move is limited only in the positive direction.
VERIFIED REPAIR
Limit the move to [-max_step, max_step].
Unsuccessful approach: Using abs(err) loses the direction of eastward moves.
Case contract
Angles in millidegrees. The target is clamped to [lo, hi]. If |target - current| < deadband no move is made. Otherwise the move is limited to +-max_step, converted to whole motor steps of dps millidegrees truncated toward zero. Return [new position, signed steps].
Why this case matters
Single-axis and dual-axis solar trackers turn a sun direction into actuator commands; a sign, frame or limit mistake points a whole plant away from the sun or into a mechanical stop.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(target, current, deadband, max_step, dps, lo, hi):
tgt = min(max(target, lo), hi)
err = tgt - current
if abs(err) < deadband:
return [current, 0]
move = min(max_step, err)
steps = abs(move) // dps
if move < 0:
steps = -steps
return [current + steps * dps, steps]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [87, 3902, 250, 500, 90, -52000, 55000], [3452, -5]],
['regression: slew limit direction (partial repair)',
[-54117, 53470, 0, 5000, 300, -55000, 52000], [48670, -16]],
['control 1', [-57417, -56117, 500, 2000, 300, -52000, 60000], [-54317, 6]],
['control 2', [-62089, -29689, 500, 5000, 300, -55000, 60000], [-34489, -16]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [-54271, -9833, 0, 2000, 90, -52000, 52000], [-11813, -22]],
['regression: slew limit direction (partial repair)',
[-47305, -34773, 1000, 5000, 300, -60000, 55000], [-39573, -16]],
['control 1', [48241, -7501, 500, 500, 125, -55000, 60000], [-7001, 4]],
['control 2', [53151, 50604, 0, 5000, 7, -55000, 55000], [53145, 363]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [25571, 48224, 500, 2000, 90, -52000, 55000], [46244, -22]],
['regression: slew limit direction (partial repair)', [5973, 21303, 250, 5000, 90, -60000, 60000],
[16353, -55]],
['control 1', [-63679, -20673, 250, 5000, 125, -52000, 60000], [-25673, -40]],
['control 2', [13642, -49671, 1000, 5000, 7, -52000, 55000], [-44673, 714]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [-50922, -33896, 1000, 2000, 7, -52000, 60000],
[-35891, -285]],
['regression: slew limit direction (partial repair)',
[-63465, 50668, 250, 500, 125, -60000, 60000], [50168, -4]],
['control 1', [-66913, 56864, 250, 5000, 125, -52000, 52000], [51864, -40]],
['control 2', [19310, -10964, 250, 5000, 125, -52000, 52000], [-5964, 40]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [3491, 18980, 250, 2000, 7, -55000, 60000], [16985, -285]],
['regression: slew limit direction (partial repair)',
[-51328, 31221, 1000, 2000, 90, -55000, 52000], [29241, -22]],
['control 1', [22704, 24328, 1000, 5000, 300, -60000, 55000], [22828, -5]],
['control 2', [56539, -49336, 500, 2000, 7, -60000, 55000], [-47341, 285]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: error equal to deadband | [1500, 4] | [1500, 4] | Passed |
| boundary: target past west stop | [59994, 142] | [59994, 142] | Passed |
| normal: negative move truncation | [-9900, -33] | [-1800, -6] | Failed |
| regression: slew limit direction | [122, -42] | [3452, -5] | Failed |
| regression: slew limit direction (partial repair) | [-53930, -358] | [48670, -16] | Failed |
| control 1 | [-54317, 6] | [-54317, 6] | Passed |
| control 2 | [-54889, -84] | [-34489, -16] | Failed |
SHA-256 / 02ec465cdae711f5c2fe0a7b017b05479eaf96a8a3f3c372d66b29f6489a5c26
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(target, current, deadband, max_step, dps, lo, hi):
tgt = min(max(target, lo), hi)
err = tgt - current
if abs(err) < deadband:
return [current, 0]
move = min(max_step, abs(err))
steps = abs(move) // dps
if move < 0:
steps = -steps
return [current + steps * dps, steps]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [87, 3902, 250, 500, 90, -52000, 55000], [3452, -5]],
['regression: slew limit direction (partial repair)',
[-54117, 53470, 0, 5000, 300, -55000, 52000], [48670, -16]],
['control 1', [-57417, -56117, 500, 2000, 300, -52000, 60000], [-54317, 6]],
['control 2', [-62089, -29689, 500, 5000, 300, -55000, 60000], [-34489, -16]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [-54271, -9833, 0, 2000, 90, -52000, 52000], [-11813, -22]],
['regression: slew limit direction (partial repair)',
[-47305, -34773, 1000, 5000, 300, -60000, 55000], [-39573, -16]],
['control 1', [48241, -7501, 500, 500, 125, -55000, 60000], [-7001, 4]],
['control 2', [53151, 50604, 0, 5000, 7, -55000, 55000], [53145, 363]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [25571, 48224, 500, 2000, 90, -52000, 55000], [46244, -22]],
['regression: slew limit direction (partial repair)', [5973, 21303, 250, 5000, 90, -60000, 60000],
[16353, -55]],
['control 1', [-63679, -20673, 250, 5000, 125, -52000, 60000], [-25673, -40]],
['control 2', [13642, -49671, 1000, 5000, 7, -52000, 55000], [-44673, 714]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [-50922, -33896, 1000, 2000, 7, -52000, 60000],
[-35891, -285]],
['regression: slew limit direction (partial repair)',
[-63465, 50668, 250, 500, 125, -60000, 60000], [50168, -4]],
['control 1', [-66913, 56864, 250, 5000, 125, -52000, 52000], [51864, -40]],
['control 2', [19310, -10964, 250, 5000, 125, -52000, 52000], [-5964, 40]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [3491, 18980, 250, 2000, 7, -55000, 60000], [16985, -285]],
['regression: slew limit direction (partial repair)',
[-51328, 31221, 1000, 2000, 90, -55000, 52000], [29241, -22]],
['control 1', [22704, 24328, 1000, 5000, 300, -60000, 55000], [22828, -5]],
['control 2', [56539, -49336, 500, 2000, 7, -60000, 55000], [-47341, 285]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: error equal to deadband | [1500, 4] | [1500, 4] | Passed |
| boundary: target past west stop | [59994, 142] | [59994, 142] | Passed |
| normal: negative move truncation | [1800, 6] | [-1800, -6] | Failed |
| regression: slew limit direction | [4352, 5] | [3452, -5] | Failed |
| regression: slew limit direction (partial repair) | [58270, 16] | [48670, -16] | Failed |
| control 1 | [-54317, 6] | [-54317, 6] | Passed |
| control 2 | [-24889, 16] | [-34489, -16] | Failed |
SHA-256 / 0cd889e87de21a67552c5de5b69e6728be26fabf74179de838b5fc007a96ee61
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(target, current, deadband, max_step, dps, lo, hi):
tgt = min(max(target, lo), hi)
err = tgt - current
if abs(err) < deadband:
return [current, 0]
move = max(-max_step, min(max_step, err))
steps = abs(move) // dps
if move < 0:
steps = -steps
return [current + steps * dps, steps]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [87, 3902, 250, 500, 90, -52000, 55000], [3452, -5]],
['regression: slew limit direction (partial repair)',
[-54117, 53470, 0, 5000, 300, -55000, 52000], [48670, -16]],
['control 1', [-57417, -56117, 500, 2000, 300, -52000, 60000], [-54317, 6]],
['control 2', [-62089, -29689, 500, 5000, 300, -55000, 60000], [-34489, -16]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [-54271, -9833, 0, 2000, 90, -52000, 52000], [-11813, -22]],
['regression: slew limit direction (partial repair)',
[-47305, -34773, 1000, 5000, 300, -60000, 55000], [-39573, -16]],
['control 1', [48241, -7501, 500, 500, 125, -55000, 60000], [-7001, 4]],
['control 2', [53151, 50604, 0, 5000, 7, -55000, 55000], [53145, 363]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [25571, 48224, 500, 2000, 90, -52000, 55000], [46244, -22]],
['regression: slew limit direction (partial repair)', [5973, 21303, 250, 5000, 90, -60000, 60000],
[16353, -55]],
['control 1', [-63679, -20673, 250, 5000, 125, -52000, 60000], [-25673, -40]],
['control 2', [13642, -49671, 1000, 5000, 7, -52000, 55000], [-44673, 714]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [-50922, -33896, 1000, 2000, 7, -52000, 60000],
[-35891, -285]],
['regression: slew limit direction (partial repair)',
[-63465, 50668, 250, 500, 125, -60000, 60000], [50168, -4]],
['control 1', [-66913, 56864, 250, 5000, 125, -52000, 52000], [51864, -40]],
['control 2', [19310, -10964, 250, 5000, 125, -52000, 52000], [-5964, 40]]],
[['boundary: error equal to deadband', [1500, 1000, 500, 2000, 125, -60000, 60000], [1500, 4]],
['boundary: target past west stop', [80000, 59000, 250, 5000, 7, -60000, 60000], [59994, 142]],
['normal: negative move truncation', [-10000, 0, 250, 2000, 300, -60000, 60000], [-1800, -6]],
['regression: slew limit direction', [3491, 18980, 250, 2000, 7, -55000, 60000], [16985, -285]],
['regression: slew limit direction (partial repair)',
[-51328, 31221, 1000, 2000, 90, -55000, 52000], [29241, -22]],
['control 1', [22704, 24328, 1000, 5000, 300, -60000, 55000], [22828, -5]],
['control 2', [56539, -49336, 500, 2000, 7, -60000, 55000], [-47341, 285]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: error equal to deadband | [1500, 4] | [1500, 4] | Passed |
| boundary: target past west stop | [59994, 142] | [59994, 142] | Passed |
| normal: negative move truncation | [-1800, -6] | [-1800, -6] | Passed |
| regression: slew limit direction | [3452, -5] | [3452, -5] | Passed |
| regression: slew limit direction (partial repair) | [48670, -16] | [48670, -16] | Passed |
| control 1 | [-54317, 6] | [-54317, 6] | Passed |
| control 2 | [-34489, -16] | [-34489, -16] | Passed |
SHA-256 / 3d47295710e32ea1179bfa6a718666039e0072d48ce0fead42a2085a6af9983f
Verification & scope
Deterministic stipulated toy contract for teaching; no claim of conformance with any standard, vendor protocol or production controller. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:51:55.938030+00:00.
Case digest / 4b0d49db7e2e862d2e62fbe54852e4ff7dff8d670bcc1230002e87baadd52f19