FAILURE MAP
← Case archive

FA-87816 / Collision detection broadphase / Open access

Rotated proxy uses signed cosine and sine · case 01

Boxes rotated into the second quadrant get inverted, too small bounds.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The absolute values are missing.

VERIFIED REPAIR

Use |cos| and |sin|.

Unsuccessful approach: Taking abs of cosine only leaves negative sine terms.

Case contract

solve(cx, cy, hx, hy, angle, margin): broadphase proxy for a box with centre, half extents and rotation in degrees. Half sizes ex=|cos|hx+|sin|hy, ey=|sin|hx+|cos|hy; fatten by margin on every side. Return [minx, miny, maxx, maxy] rounded to 6.

Why this case matters

Broadphase stages decide which object pairs ever reach narrowphase; a wrong boundary, ordering or bookkeeping rule silently drops real contacts or floods the solver with false candidates.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cx, cy, hx, hy, angle, margin):
    c, s = math.cos(math.radians(angle)), math.sin(math.radians(angle))
    ex = c * hx + s * hy
    ey = s * hx + c * hy
    return [round(cx - ex - margin, 6), round(cy - ey - margin, 6), round(cx + ex + margin, 6), round(cy + ey + margin, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.91, -1.75, 0.35, 1.22, 315, 0.1], [-0.300158, -2.960158, 2.120158, -0.539842]], [[4.84, 4.55, 2.87, 2.62, -60, 0.1], [1.036013, 0.654507, 8.643987, 8.445493]], [[1.77, -3.53, 2.26, 2.35, 30, 0.1], [-1.462217, -6.79516, 5.002217, -0.26484]], [[0.72, 3.51, 1.08, 2.11, 120, 0.1], [-1.747314, 1.419693, 3.187314, 5.600307]], [[3.9, 2.87, 0.78, 2.52, 30, 0.1], [1.8645, 0.197616, 5.9355, 5.542384]], [[4.14, -0.89, 0.57, 1.72, 200, 0.25], [2.766101, -2.951223, 5.513899, 1.171223]], [[3.98, -3.59, 1.92, 2.16, 315, 0.25], [0.845004, -6.724996, 7.114996, -0.455004]], [[0.94, 2.55, 2.56, 0.61, 30, 0.0], [-1.582025, 0.741725, 3.462025, 4.358275]]], [[[4.14, 1.21, 1.43, 0.9, -60, 0.0], [2.645577, -0.478416, 5.634423, 2.898416]], [[-2.93, -1.95, 0.58, 1.23, 200, 0.25], [-4.145706, -3.554194, -1.714294, -0.345806]], [[4.97, -3.01, 0.79, 0.48, 315, 0.0], [4.071974, -3.908026, 5.868026, -2.111974]], [[0.24, 0.41, 0.98, 0.96, 45, 0.25], [-1.381787, -1.211787, 1.861787, 2.031787]], [[3.82, 1.95, 1.76, 0.82, 315, 0.0], [1.995665, 0.125665, 5.644335, 3.774335]], [[4.72, -3.73, 0.89, 0.86, 30, 0.25], [3.269237, -5.169782, 6.170763, -2.290218]], [[0.48, -3.95, 0.42, 0.48, 0, 0.25], [-0.19, -4.68, 1.15, -3.22]], [[-3.76, -4.4, 1.23, 2.09, -60, 0.25], [-6.434993, -6.760211, -1.085007, -2.039789]]], [[[0.06, 3.56, 0.41, 0.95, 200, 0.0], [-0.650193, 2.527064, 0.770193, 4.592936]], [[-4.03, -0.42, 2.55, 2.24, -60, 0.1], [-7.344897, -3.848365, -0.715103, 3.008365]], [[-4.07, 1.15, 1.87, 2.11, 45, 0.25], [-7.134285, -1.914285, -1.005715, 4.214285]], [[3.78, 2.5, 2.46, 2.65, 30, 0.25], [0.074578, -1.274967, 7.485422, 6.274967]], [[-3.34, -2.28, 2.85, 2.54, 120, 0.1], [-7.064705, -6.118172, 0.384705, 1.558172]], [[4.54, -1.65, 1.92, 0.9, 45, 0.1], [2.445959, -3.744041, 6.634041, 0.444041]], [[4.87, -3.44, 1.72, 2.36, 200, 0.1], [2.346561, -6.345949, 7.393439, -0.534051]], [[-4.67, 4.48, 2.36, 1.01, 200, 0.25], [-7.483115, 2.473743, -1.856885, 6.486257]]], [[[2.09, 2.94, 1.51, 1.33, -60, 0.25], [-0.066814, 0.717302, 4.246814, 5.162698]], [[-1.1, -2.32, 1.47, 1.52, 90, 0.1], [-2.72, -3.89, 0.52, -0.75]], [[1.18, 1.1, 0.22, 2.53, 120, 0.25], [-1.371044, -0.605526, 3.731044, 2.805526]], [[-3.34, -2.26, 0.21, 0.6, 200, 0.25], [-3.992548, -3.14564, -2.687452, -1.37436]], [[-2.9, -3.38, 0.84, 1.4, -60, 0.25], [-4.782436, -5.057461, -1.017564, -1.702539]], [[-1.94, 2.72, 2.91, 1.79, 0, 0.1], [-4.95, 0.83, 1.07, 4.61]], [[0.24, -3.7, 0.4, 0.93, 90, 0.1], [-0.79, -4.2, 1.27, -3.2]], [[4.03, 0.68, 2.73, 2.08, -60, 0.1], [0.763667, -2.824249, 7.296333, 4.184249]]], [[[-2.8, -1.67, 0.46, 2.0, 30, 0.1], [-4.298372, -3.732051, -1.301628, 0.392051]], [[-1.02, 4.19, 2.82, 1.9, 315, 0.1], [-4.457544, 0.752456, 2.417544, 7.627544]], [[-2.22, -2.6, 0.84, 1.79, 0, 0.0], [-3.06, -4.39, -1.38, -0.81]], [[-1.12, -3.66, 1.94, 1.1, 315, 0.1], [-3.369605, -5.909605, 1.129605, -1.410395]], [[-0.37, -0.12, 0.29, 1.91, 45, 0.25], [-2.175635, -1.925635, 1.435635, 1.685635]], [[-1.43, 1.82, 0.42, 1.72, 30, 0.0], [-2.653731, 0.120436, -0.206269, 3.519564]], [[-1.51, -4.04, 1.87, 1.15, -60, 0.1], [-3.540929, -6.334468, 0.520929, -1.745532]], [[3.71, -4.41, 2.18, 2.5, -60, 0.1], [0.354936, -7.647935, 7.065064, -1.172065]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("case %d" % i, 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 fixtureActualExpectedOutcome
case 0[1.425183, -2.465183, 0.394817, -1.034817][-0.300158, -2.960158, 2.120158, -0.539842]Failed
case 1[5.573987, 5.625493, 4.106013, 3.474507][1.036013, 0.654507, 8.643987, 8.445493]Failed
case 2[-1.462217, -6.79516, 5.002217, -0.26484][-1.462217, -6.79516, 5.002217, -0.26484]Passed
case 3[-0.667314, 3.529693, 2.107314, 3.490307][-1.747314, 1.419693, 3.187314, 5.600307]Failed
case 4[1.8645, 0.197616, 5.9355, 5.542384][1.8645, 0.197616, 5.9355, 5.542384]Passed
case 5[5.013899, 0.671223, 3.266101, -2.451223][2.766101, -2.951223, 5.513899, 1.171223]Failed
case 6[3.899706, -4.009706, 4.060294, -3.170294][0.845004, -6.724996, 7.114996, -0.455004]Failed
case 7[-1.582025, 0.741725, 3.462025, 4.358275][-1.582025, 0.741725, 3.462025, 4.358275]Passed

SHA-256 / b986e3bc397d8a96ab60d83afec3133034743ba00280495d89f20cad794e7e81

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cx, cy, hx, hy, angle, margin):
    c, s = abs(math.cos(math.radians(angle))), math.sin(math.radians(angle))
    ex = c * hx + s * hy
    ey = s * hx + c * hy
    return [round(cx - ex - margin, 6), round(cy - ey - margin, 6), round(cx + ex + margin, 6), round(cy + ey + margin, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.91, -1.75, 0.35, 1.22, 315, 0.1], [-0.300158, -2.960158, 2.120158, -0.539842]], [[4.84, 4.55, 2.87, 2.62, -60, 0.1], [1.036013, 0.654507, 8.643987, 8.445493]], [[1.77, -3.53, 2.26, 2.35, 30, 0.1], [-1.462217, -6.79516, 5.002217, -0.26484]], [[0.72, 3.51, 1.08, 2.11, 120, 0.1], [-1.747314, 1.419693, 3.187314, 5.600307]], [[3.9, 2.87, 0.78, 2.52, 30, 0.1], [1.8645, 0.197616, 5.9355, 5.542384]], [[4.14, -0.89, 0.57, 1.72, 200, 0.25], [2.766101, -2.951223, 5.513899, 1.171223]], [[3.98, -3.59, 1.92, 2.16, 315, 0.25], [0.845004, -6.724996, 7.114996, -0.455004]], [[0.94, 2.55, 2.56, 0.61, 30, 0.0], [-1.582025, 0.741725, 3.462025, 4.358275]]], [[[4.14, 1.21, 1.43, 0.9, -60, 0.0], [2.645577, -0.478416, 5.634423, 2.898416]], [[-2.93, -1.95, 0.58, 1.23, 200, 0.25], [-4.145706, -3.554194, -1.714294, -0.345806]], [[4.97, -3.01, 0.79, 0.48, 315, 0.0], [4.071974, -3.908026, 5.868026, -2.111974]], [[0.24, 0.41, 0.98, 0.96, 45, 0.25], [-1.381787, -1.211787, 1.861787, 2.031787]], [[3.82, 1.95, 1.76, 0.82, 315, 0.0], [1.995665, 0.125665, 5.644335, 3.774335]], [[4.72, -3.73, 0.89, 0.86, 30, 0.25], [3.269237, -5.169782, 6.170763, -2.290218]], [[0.48, -3.95, 0.42, 0.48, 0, 0.25], [-0.19, -4.68, 1.15, -3.22]], [[-3.76, -4.4, 1.23, 2.09, -60, 0.25], [-6.434993, -6.760211, -1.085007, -2.039789]]], [[[0.06, 3.56, 0.41, 0.95, 200, 0.0], [-0.650193, 2.527064, 0.770193, 4.592936]], [[-4.03, -0.42, 2.55, 2.24, -60, 0.1], [-7.344897, -3.848365, -0.715103, 3.008365]], [[-4.07, 1.15, 1.87, 2.11, 45, 0.25], [-7.134285, -1.914285, -1.005715, 4.214285]], [[3.78, 2.5, 2.46, 2.65, 30, 0.25], [0.074578, -1.274967, 7.485422, 6.274967]], [[-3.34, -2.28, 2.85, 2.54, 120, 0.1], [-7.064705, -6.118172, 0.384705, 1.558172]], [[4.54, -1.65, 1.92, 0.9, 45, 0.1], [2.445959, -3.744041, 6.634041, 0.444041]], [[4.87, -3.44, 1.72, 2.36, 200, 0.1], [2.346561, -6.345949, 7.393439, -0.534051]], [[-4.67, 4.48, 2.36, 1.01, 200, 0.25], [-7.483115, 2.473743, -1.856885, 6.486257]]], [[[2.09, 2.94, 1.51, 1.33, -60, 0.25], [-0.066814, 0.717302, 4.246814, 5.162698]], [[-1.1, -2.32, 1.47, 1.52, 90, 0.1], [-2.72, -3.89, 0.52, -0.75]], [[1.18, 1.1, 0.22, 2.53, 120, 0.25], [-1.371044, -0.605526, 3.731044, 2.805526]], [[-3.34, -2.26, 0.21, 0.6, 200, 0.25], [-3.992548, -3.14564, -2.687452, -1.37436]], [[-2.9, -3.38, 0.84, 1.4, -60, 0.25], [-4.782436, -5.057461, -1.017564, -1.702539]], [[-1.94, 2.72, 2.91, 1.79, 0, 0.1], [-4.95, 0.83, 1.07, 4.61]], [[0.24, -3.7, 0.4, 0.93, 90, 0.1], [-0.79, -4.2, 1.27, -3.2]], [[4.03, 0.68, 2.73, 2.08, -60, 0.1], [0.763667, -2.824249, 7.296333, 4.184249]]], [[[-2.8, -1.67, 0.46, 2.0, 30, 0.1], [-4.298372, -3.732051, -1.301628, 0.392051]], [[-1.02, 4.19, 2.82, 1.9, 315, 0.1], [-4.457544, 0.752456, 2.417544, 7.627544]], [[-2.22, -2.6, 0.84, 1.79, 0, 0.0], [-3.06, -4.39, -1.38, -0.81]], [[-1.12, -3.66, 1.94, 1.1, 315, 0.1], [-3.369605, -5.909605, 1.129605, -1.410395]], [[-0.37, -0.12, 0.29, 1.91, 45, 0.25], [-2.175635, -1.925635, 1.435635, 1.685635]], [[-1.43, 1.82, 0.42, 1.72, 30, 0.0], [-2.653731, 0.120436, -0.206269, 3.519564]], [[-1.51, -4.04, 1.87, 1.15, -60, 0.1], [-3.540929, -6.334468, 0.520929, -1.745532]], [[3.71, -4.41, 2.18, 2.5, -60, 0.1], [0.354936, -7.647935, 7.065064, -1.172065]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("case %d" % i, 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 fixtureActualExpectedOutcome
case 0[1.425183, -2.465183, 0.394817, -1.034817][-0.300158, -2.960158, 2.120158, -0.539842]Failed
case 1[5.573987, 5.625493, 4.106013, 3.474507][1.036013, 0.654507, 8.643987, 8.445493]Failed
case 2[-1.462217, -6.79516, 5.002217, -0.26484][-1.462217, -6.79516, 5.002217, -0.26484]Passed
case 3[-1.747314, 1.419693, 3.187314, 5.600307][-1.747314, 1.419693, 3.187314, 5.600307]Passed
case 4[1.8645, 0.197616, 5.9355, 5.542384][1.8645, 0.197616, 5.9355, 5.542384]Passed
case 5[3.94265, -2.56132, 4.33735, 0.78132][2.766101, -2.951223, 5.513899, 1.171223]Failed
case 6[3.899706, -4.009706, 4.060294, -3.170294][0.845004, -6.724996, 7.114996, -0.455004]Failed
case 7[-1.582025, 0.741725, 3.462025, 4.358275][-1.582025, 0.741725, 3.462025, 4.358275]Passed

SHA-256 / e6f394f302e827075c15580e29aaed8258889dace83388b1db84d83382dac219

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cx, cy, hx, hy, angle, margin):
    c, s = abs(math.cos(math.radians(angle))), abs(math.sin(math.radians(angle)))
    ex = c * hx + s * hy
    ey = s * hx + c * hy
    return [round(cx - ex - margin, 6), round(cy - ey - margin, 6), round(cx + ex + margin, 6), round(cy + ey + margin, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.91, -1.75, 0.35, 1.22, 315, 0.1], [-0.300158, -2.960158, 2.120158, -0.539842]], [[4.84, 4.55, 2.87, 2.62, -60, 0.1], [1.036013, 0.654507, 8.643987, 8.445493]], [[1.77, -3.53, 2.26, 2.35, 30, 0.1], [-1.462217, -6.79516, 5.002217, -0.26484]], [[0.72, 3.51, 1.08, 2.11, 120, 0.1], [-1.747314, 1.419693, 3.187314, 5.600307]], [[3.9, 2.87, 0.78, 2.52, 30, 0.1], [1.8645, 0.197616, 5.9355, 5.542384]], [[4.14, -0.89, 0.57, 1.72, 200, 0.25], [2.766101, -2.951223, 5.513899, 1.171223]], [[3.98, -3.59, 1.92, 2.16, 315, 0.25], [0.845004, -6.724996, 7.114996, -0.455004]], [[0.94, 2.55, 2.56, 0.61, 30, 0.0], [-1.582025, 0.741725, 3.462025, 4.358275]]], [[[4.14, 1.21, 1.43, 0.9, -60, 0.0], [2.645577, -0.478416, 5.634423, 2.898416]], [[-2.93, -1.95, 0.58, 1.23, 200, 0.25], [-4.145706, -3.554194, -1.714294, -0.345806]], [[4.97, -3.01, 0.79, 0.48, 315, 0.0], [4.071974, -3.908026, 5.868026, -2.111974]], [[0.24, 0.41, 0.98, 0.96, 45, 0.25], [-1.381787, -1.211787, 1.861787, 2.031787]], [[3.82, 1.95, 1.76, 0.82, 315, 0.0], [1.995665, 0.125665, 5.644335, 3.774335]], [[4.72, -3.73, 0.89, 0.86, 30, 0.25], [3.269237, -5.169782, 6.170763, -2.290218]], [[0.48, -3.95, 0.42, 0.48, 0, 0.25], [-0.19, -4.68, 1.15, -3.22]], [[-3.76, -4.4, 1.23, 2.09, -60, 0.25], [-6.434993, -6.760211, -1.085007, -2.039789]]], [[[0.06, 3.56, 0.41, 0.95, 200, 0.0], [-0.650193, 2.527064, 0.770193, 4.592936]], [[-4.03, -0.42, 2.55, 2.24, -60, 0.1], [-7.344897, -3.848365, -0.715103, 3.008365]], [[-4.07, 1.15, 1.87, 2.11, 45, 0.25], [-7.134285, -1.914285, -1.005715, 4.214285]], [[3.78, 2.5, 2.46, 2.65, 30, 0.25], [0.074578, -1.274967, 7.485422, 6.274967]], [[-3.34, -2.28, 2.85, 2.54, 120, 0.1], [-7.064705, -6.118172, 0.384705, 1.558172]], [[4.54, -1.65, 1.92, 0.9, 45, 0.1], [2.445959, -3.744041, 6.634041, 0.444041]], [[4.87, -3.44, 1.72, 2.36, 200, 0.1], [2.346561, -6.345949, 7.393439, -0.534051]], [[-4.67, 4.48, 2.36, 1.01, 200, 0.25], [-7.483115, 2.473743, -1.856885, 6.486257]]], [[[2.09, 2.94, 1.51, 1.33, -60, 0.25], [-0.066814, 0.717302, 4.246814, 5.162698]], [[-1.1, -2.32, 1.47, 1.52, 90, 0.1], [-2.72, -3.89, 0.52, -0.75]], [[1.18, 1.1, 0.22, 2.53, 120, 0.25], [-1.371044, -0.605526, 3.731044, 2.805526]], [[-3.34, -2.26, 0.21, 0.6, 200, 0.25], [-3.992548, -3.14564, -2.687452, -1.37436]], [[-2.9, -3.38, 0.84, 1.4, -60, 0.25], [-4.782436, -5.057461, -1.017564, -1.702539]], [[-1.94, 2.72, 2.91, 1.79, 0, 0.1], [-4.95, 0.83, 1.07, 4.61]], [[0.24, -3.7, 0.4, 0.93, 90, 0.1], [-0.79, -4.2, 1.27, -3.2]], [[4.03, 0.68, 2.73, 2.08, -60, 0.1], [0.763667, -2.824249, 7.296333, 4.184249]]], [[[-2.8, -1.67, 0.46, 2.0, 30, 0.1], [-4.298372, -3.732051, -1.301628, 0.392051]], [[-1.02, 4.19, 2.82, 1.9, 315, 0.1], [-4.457544, 0.752456, 2.417544, 7.627544]], [[-2.22, -2.6, 0.84, 1.79, 0, 0.0], [-3.06, -4.39, -1.38, -0.81]], [[-1.12, -3.66, 1.94, 1.1, 315, 0.1], [-3.369605, -5.909605, 1.129605, -1.410395]], [[-0.37, -0.12, 0.29, 1.91, 45, 0.25], [-2.175635, -1.925635, 1.435635, 1.685635]], [[-1.43, 1.82, 0.42, 1.72, 30, 0.0], [-2.653731, 0.120436, -0.206269, 3.519564]], [[-1.51, -4.04, 1.87, 1.15, -60, 0.1], [-3.540929, -6.334468, 0.520929, -1.745532]], [[3.71, -4.41, 2.18, 2.5, -60, 0.1], [0.354936, -7.647935, 7.065064, -1.172065]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("case %d" % i, 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 fixtureActualExpectedOutcome
case 0[-0.300158, -2.960158, 2.120158, -0.539842][-0.300158, -2.960158, 2.120158, -0.539842]Passed
case 1[1.036013, 0.654507, 8.643987, 8.445493][1.036013, 0.654507, 8.643987, 8.445493]Passed
case 2[-1.462217, -6.79516, 5.002217, -0.26484][-1.462217, -6.79516, 5.002217, -0.26484]Passed
case 3[-1.747314, 1.419693, 3.187314, 5.600307][-1.747314, 1.419693, 3.187314, 5.600307]Passed
case 4[1.8645, 0.197616, 5.9355, 5.542384][1.8645, 0.197616, 5.9355, 5.542384]Passed
case 5[2.766101, -2.951223, 5.513899, 1.171223][2.766101, -2.951223, 5.513899, 1.171223]Passed
case 6[0.845004, -6.724996, 7.114996, -0.455004][0.845004, -6.724996, 7.114996, -0.455004]Passed
case 7[-1.582025, 0.741725, 3.462025, 4.358275][-1.582025, 0.741725, 3.462025, 4.358275]Passed

SHA-256 / ac6cf5b27be1c56856c4bb24f2b58399d337b6a6da814d7d82689a85caf35dd2

Verification & scope

A deterministic bounded teaching model with stipulated toy conventions; not a production physics engine or spatial index. 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:02.365351+00:00.

Case digest / ee3ca99f8d793535cbdb86b46b2a45b35c04ee35eba74c5c4a65f82ffaaf5ba8