FA-87826 / Collision detection broadphase / Open access
Rotation angle in degrees is passed to trig as radians · case 01
Proxy bounds wobble unpredictably with rotation.
ROOT CAUSE
math.cos/sin receive degrees.
VERIFIED REPAIR
Convert degrees to radians first.
Unsuccessful approach: Converting only the cosine argument desynchronizes the two 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 = abs(math.cos(angle)), abs(math.sin(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 = [[[[-4.92, -4.34, 0.9, 1.57, 45, 0.1], [-6.766554, -6.186554, -3.073446, -2.493446]], [[1.84, -3.67, 1.72, 0.21, 200, 0.25], [-0.098096, -4.70561, 3.778096, -2.63439]], [[0.02, -4.86, 1.53, 2.94, 0, 0.25], [-1.76, -8.05, 1.8, -1.67]], [[-2.89, 3.75, 2.94, 0.83, 90, 0.1], [-3.82, 0.71, -1.96, 6.79]], [[2.2, -0.16, 0.47, 2.58, 0, 0.1], [1.63, -2.84, 2.77, 2.52]], [[3.9, 2.87, 0.78, 2.52, 30, 0.1], [1.8645, 0.197616, 5.9355, 5.542384]], [[3.13, 2.46, 0.75, 1.11, 0, 0.25], [2.13, 1.1, 4.13, 3.82]], [[-3.39, 3.94, 1.23, 2.92, 30, 0.25], [-6.165211, 0.546206, -0.614789, 7.333794]]], [[[3.37, 4.9, 1.24, 1.52, 90, 0.0], [1.85, 3.66, 4.89, 6.14]], [[-0.99, 4.24, 1.28, 0.63, 200, 0.1], [-2.508279, 3.110208, 0.528279, 5.369792]], [[-4.29, 1.39, 0.51, 0.98, 0, 0.0], [-4.8, 0.41, -3.78, 2.37]], [[-2.89, 4.67, 2.63, 2.44, 45, 0.1], [-6.575031, 0.984969, 0.795031, 8.355031]], [[-1.14, 1.51, 2.36, 2.73, 0, 0.1], [-3.6, -1.32, 1.32, 4.34]], [[0.46, -2.18, 2.96, 2.99, 0, 0.1], [-2.6, -5.27, 3.52, 0.91]], [[-4.51, -4.28, 2.68, 0.57, 30, 0.0], [-7.115948, -6.113634, -1.904052, -2.446366]], [[4.41, -1.11, 1.75, 0.58, 200, 0.0], [2.567166, -2.253557, 6.252834, 0.033557]]], [[[3.29, -2.89, 0.26, 2.98, 315, 0.25], [0.748974, -5.431026, 5.831026, -0.348974]], [[-2.52, 0.96, 2.59, 2.81, 120, 0.25], [-6.498531, -2.938006, 1.458531, 4.858006]], [[0.68, 3.48, 1.98, 1.38, 0, 0.0], [-1.3, 2.1, 2.66, 4.86]], [[-2.45, -1.5, 2.08, 2.29, 30, 0.1], [-5.496333, -4.623198, 0.596333, 1.623198]], [[3.12, 3.25, 0.64, 2.46, 45, 0.25], [0.677969, 0.807969, 5.562031, 5.692031]], [[-1.95, -2.46, 0.54, 1.03, 0, 0.25], [-2.74, -3.74, -1.16, -1.18]], [[-4.53, -3.28, 0.71, 2.74, 0, 0.0], [-5.24, -6.02, -3.82, -0.54]], [[2.99, 4.84, 1.0, 2.55, 90, 0.1], [0.34, 3.74, 5.64, 5.94]]], [[[-2.9, -3.38, 0.84, 1.4, -60, 0.25], [-4.782436, -5.057461, -1.017564, -1.702539]], [[1.89, -0.65, 0.83, 1.04, 90, 0.25], [0.6, -1.73, 3.18, 0.43]], [[4.74, -1.56, 2.8, 2.68, 200, 0.1], [1.092247, -5.136033, 8.387753, 2.016033]], [[-4.44, -1.42, 0.24, 2.68, 0, 0.0], [-4.68, -4.1, -4.2, 1.26]], [[-3.23, -1.53, 1.58, 1.01, 0, 0.1], [-4.91, -2.64, -1.55, -0.42]], [[-4.11, -4.57, 0.35, 1.82, 30, 0.1], [-5.423109, -6.421166, -2.796891, -2.718834]], [[-1.24, -3.47, 2.98, 2.05, 0, 0.25], [-4.47, -5.77, 1.99, -1.17]], [[-4.86, -3.84, 2.72, 0.87, 90, 0.1], [-5.83, -6.66, -3.89, -1.02]]], [[[-3.04, 2.12, 1.02, 1.96, 90, 0.25], [-5.25, 0.85, -0.83, 3.39]], [[0.03, -1.98, 2.08, 0.65, 45, 0.1], [-2.000402, -4.010402, 2.060402, 0.050402]], [[4.18, -1.21, 1.82, 2.07, 45, 0.0], [1.429355, -3.960645, 6.930645, 1.540645]], [[2.08, -3.48, 0.39, 0.88, 30, 0.1], [1.20225, -4.537102, 2.95775, -2.422898]], [[-3.63, 2.62, 0.49, 0.4, -60, 0.0], [-4.22141, 1.995648, -3.03859, 3.244352]], [[4.13, -2.65, 2.98, 1.36, 0, 0.25], [0.9, -4.26, 7.36, -1.04]], [[-4.65, -3.77, 1.53, 2.23, 0, 0.0], [-6.18, -6.0, -3.12, -1.54]], [[1.9, 2.74, 0.94, 2.66, 0, 0.1], [0.86, -0.02, 2.94, 5.5]]]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| case 0 | [-6.828708, -6.030569, -3.011292, -2.649431] | [-6.766554, -6.186554, -3.073446, -2.493446] | Failed |
| case 1 | [0.568645, -5.524381, 3.111355, -1.815619] | [-0.098096, -4.70561, 3.778096, -2.63439] | Failed |
| case 2 | [-1.76, -8.05, 1.8, -1.67] | [-1.76, -8.05, 1.8, -1.67] | Passed |
| case 3 | [-5.049354, 0.649749, -0.730646, 6.850251] | [-3.82, 0.71, -1.96, 6.79] | Failed |
| case 4 | [1.63, -2.84, 2.77, 2.52] | [1.63, -2.84, 2.77, 2.52] | Passed |
| case 5 | [1.189844, 1.610622, 6.610156, 4.129378] | [1.8645, 0.197616, 5.9355, 5.542384] | Failed |
| case 6 | [2.13, 1.1, 4.13, 3.82] | [2.13, 1.1, 4.13, 3.82] | Passed |
| case 7 | [-6.714782, 2.024307, -0.065218, 5.855693] | [-6.165211, 0.546206, -0.614789, 7.333794] | Failed |
SHA-256 / fa7bf5c6f7fc02b92df8968718426d2a4edcdb67ffa32e850af09bbde3da4070
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))), abs(math.sin(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 = [[[[-4.92, -4.34, 0.9, 1.57, 45, 0.1], [-6.766554, -6.186554, -3.073446, -2.493446]], [[1.84, -3.67, 1.72, 0.21, 200, 0.25], [-0.098096, -4.70561, 3.778096, -2.63439]], [[0.02, -4.86, 1.53, 2.94, 0, 0.25], [-1.76, -8.05, 1.8, -1.67]], [[-2.89, 3.75, 2.94, 0.83, 90, 0.1], [-3.82, 0.71, -1.96, 6.79]], [[2.2, -0.16, 0.47, 2.58, 0, 0.1], [1.63, -2.84, 2.77, 2.52]], [[3.9, 2.87, 0.78, 2.52, 30, 0.1], [1.8645, 0.197616, 5.9355, 5.542384]], [[3.13, 2.46, 0.75, 1.11, 0, 0.25], [2.13, 1.1, 4.13, 3.82]], [[-3.39, 3.94, 1.23, 2.92, 30, 0.25], [-6.165211, 0.546206, -0.614789, 7.333794]]], [[[3.37, 4.9, 1.24, 1.52, 90, 0.0], [1.85, 3.66, 4.89, 6.14]], [[-0.99, 4.24, 1.28, 0.63, 200, 0.1], [-2.508279, 3.110208, 0.528279, 5.369792]], [[-4.29, 1.39, 0.51, 0.98, 0, 0.0], [-4.8, 0.41, -3.78, 2.37]], [[-2.89, 4.67, 2.63, 2.44, 45, 0.1], [-6.575031, 0.984969, 0.795031, 8.355031]], [[-1.14, 1.51, 2.36, 2.73, 0, 0.1], [-3.6, -1.32, 1.32, 4.34]], [[0.46, -2.18, 2.96, 2.99, 0, 0.1], [-2.6, -5.27, 3.52, 0.91]], [[-4.51, -4.28, 2.68, 0.57, 30, 0.0], [-7.115948, -6.113634, -1.904052, -2.446366]], [[4.41, -1.11, 1.75, 0.58, 200, 0.0], [2.567166, -2.253557, 6.252834, 0.033557]]], [[[3.29, -2.89, 0.26, 2.98, 315, 0.25], [0.748974, -5.431026, 5.831026, -0.348974]], [[-2.52, 0.96, 2.59, 2.81, 120, 0.25], [-6.498531, -2.938006, 1.458531, 4.858006]], [[0.68, 3.48, 1.98, 1.38, 0, 0.0], [-1.3, 2.1, 2.66, 4.86]], [[-2.45, -1.5, 2.08, 2.29, 30, 0.1], [-5.496333, -4.623198, 0.596333, 1.623198]], [[3.12, 3.25, 0.64, 2.46, 45, 0.25], [0.677969, 0.807969, 5.562031, 5.692031]], [[-1.95, -2.46, 0.54, 1.03, 0, 0.25], [-2.74, -3.74, -1.16, -1.18]], [[-4.53, -3.28, 0.71, 2.74, 0, 0.0], [-5.24, -6.02, -3.82, -0.54]], [[2.99, 4.84, 1.0, 2.55, 90, 0.1], [0.34, 3.74, 5.64, 5.94]]], [[[-2.9, -3.38, 0.84, 1.4, -60, 0.25], [-4.782436, -5.057461, -1.017564, -1.702539]], [[1.89, -0.65, 0.83, 1.04, 90, 0.25], [0.6, -1.73, 3.18, 0.43]], [[4.74, -1.56, 2.8, 2.68, 200, 0.1], [1.092247, -5.136033, 8.387753, 2.016033]], [[-4.44, -1.42, 0.24, 2.68, 0, 0.0], [-4.68, -4.1, -4.2, 1.26]], [[-3.23, -1.53, 1.58, 1.01, 0, 0.1], [-4.91, -2.64, -1.55, -0.42]], [[-4.11, -4.57, 0.35, 1.82, 30, 0.1], [-5.423109, -6.421166, -2.796891, -2.718834]], [[-1.24, -3.47, 2.98, 2.05, 0, 0.25], [-4.47, -5.77, 1.99, -1.17]], [[-4.86, -3.84, 2.72, 0.87, 90, 0.1], [-5.83, -6.66, -3.89, -1.02]]], [[[-3.04, 2.12, 1.02, 1.96, 90, 0.25], [-5.25, 0.85, -0.83, 3.39]], [[0.03, -1.98, 2.08, 0.65, 45, 0.1], [-2.000402, -4.010402, 2.060402, 0.050402]], [[4.18, -1.21, 1.82, 2.07, 45, 0.0], [1.429355, -3.960645, 6.930645, 1.540645]], [[2.08, -3.48, 0.39, 0.88, 30, 0.1], [1.20225, -4.537102, 2.95775, -2.422898]], [[-3.63, 2.62, 0.49, 0.4, -60, 0.0], [-4.22141, 1.995648, -3.03859, 3.244352]], [[4.13, -2.65, 2.98, 1.36, 0, 0.25], [0.9, -4.26, 7.36, -1.04]], [[-4.65, -3.77, 1.53, 2.23, 0, 0.0], [-6.18, -6.0, -3.12, -1.54]], [[1.9, 2.74, 0.94, 2.66, 0, 0.1], [0.86, -0.02, 2.94, 5.5]]]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| case 0 | [-6.992315, -6.315971, -2.847685, -2.364029] | [-6.766554, -6.186554, -3.073446, -2.493446] | Failed |
| case 1 | [-0.209664, -5.619407, 3.889664, -1.720593] | [-0.098096, -4.70561, 3.778096, -2.63439] | Failed |
| case 2 | [-1.76, -8.05, 1.8, -1.67] | [-1.76, -8.05, 1.8, -1.67] | Passed |
| case 3 | [-3.732017, 1.02165, -2.047983, 6.47835] | [-3.82, 0.71, -1.96, 6.79] | Failed |
| case 4 | [1.63, -2.84, 2.77, 2.52] | [1.63, -2.84, 2.77, 2.52] | Passed |
| case 5 | [0.63466, -0.183049, 7.16534, 5.923049] | [1.8645, 0.197616, 5.9355, 5.542384] | Failed |
| case 6 | [2.13, 1.1, 4.13, 3.82] | [2.13, 1.1, 4.13, 3.82] | Passed |
| case 7 | [-7.590264, -0.054073, 0.810264, 7.934073] | [-6.165211, 0.546206, -0.614789, 7.333794] | Failed |
SHA-256 / 56767c5aeeb6cac1b356378a91f62363e00f9f74ea71bc0296a9b52f813b0000
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 = [[[[-4.92, -4.34, 0.9, 1.57, 45, 0.1], [-6.766554, -6.186554, -3.073446, -2.493446]], [[1.84, -3.67, 1.72, 0.21, 200, 0.25], [-0.098096, -4.70561, 3.778096, -2.63439]], [[0.02, -4.86, 1.53, 2.94, 0, 0.25], [-1.76, -8.05, 1.8, -1.67]], [[-2.89, 3.75, 2.94, 0.83, 90, 0.1], [-3.82, 0.71, -1.96, 6.79]], [[2.2, -0.16, 0.47, 2.58, 0, 0.1], [1.63, -2.84, 2.77, 2.52]], [[3.9, 2.87, 0.78, 2.52, 30, 0.1], [1.8645, 0.197616, 5.9355, 5.542384]], [[3.13, 2.46, 0.75, 1.11, 0, 0.25], [2.13, 1.1, 4.13, 3.82]], [[-3.39, 3.94, 1.23, 2.92, 30, 0.25], [-6.165211, 0.546206, -0.614789, 7.333794]]], [[[3.37, 4.9, 1.24, 1.52, 90, 0.0], [1.85, 3.66, 4.89, 6.14]], [[-0.99, 4.24, 1.28, 0.63, 200, 0.1], [-2.508279, 3.110208, 0.528279, 5.369792]], [[-4.29, 1.39, 0.51, 0.98, 0, 0.0], [-4.8, 0.41, -3.78, 2.37]], [[-2.89, 4.67, 2.63, 2.44, 45, 0.1], [-6.575031, 0.984969, 0.795031, 8.355031]], [[-1.14, 1.51, 2.36, 2.73, 0, 0.1], [-3.6, -1.32, 1.32, 4.34]], [[0.46, -2.18, 2.96, 2.99, 0, 0.1], [-2.6, -5.27, 3.52, 0.91]], [[-4.51, -4.28, 2.68, 0.57, 30, 0.0], [-7.115948, -6.113634, -1.904052, -2.446366]], [[4.41, -1.11, 1.75, 0.58, 200, 0.0], [2.567166, -2.253557, 6.252834, 0.033557]]], [[[3.29, -2.89, 0.26, 2.98, 315, 0.25], [0.748974, -5.431026, 5.831026, -0.348974]], [[-2.52, 0.96, 2.59, 2.81, 120, 0.25], [-6.498531, -2.938006, 1.458531, 4.858006]], [[0.68, 3.48, 1.98, 1.38, 0, 0.0], [-1.3, 2.1, 2.66, 4.86]], [[-2.45, -1.5, 2.08, 2.29, 30, 0.1], [-5.496333, -4.623198, 0.596333, 1.623198]], [[3.12, 3.25, 0.64, 2.46, 45, 0.25], [0.677969, 0.807969, 5.562031, 5.692031]], [[-1.95, -2.46, 0.54, 1.03, 0, 0.25], [-2.74, -3.74, -1.16, -1.18]], [[-4.53, -3.28, 0.71, 2.74, 0, 0.0], [-5.24, -6.02, -3.82, -0.54]], [[2.99, 4.84, 1.0, 2.55, 90, 0.1], [0.34, 3.74, 5.64, 5.94]]], [[[-2.9, -3.38, 0.84, 1.4, -60, 0.25], [-4.782436, -5.057461, -1.017564, -1.702539]], [[1.89, -0.65, 0.83, 1.04, 90, 0.25], [0.6, -1.73, 3.18, 0.43]], [[4.74, -1.56, 2.8, 2.68, 200, 0.1], [1.092247, -5.136033, 8.387753, 2.016033]], [[-4.44, -1.42, 0.24, 2.68, 0, 0.0], [-4.68, -4.1, -4.2, 1.26]], [[-3.23, -1.53, 1.58, 1.01, 0, 0.1], [-4.91, -2.64, -1.55, -0.42]], [[-4.11, -4.57, 0.35, 1.82, 30, 0.1], [-5.423109, -6.421166, -2.796891, -2.718834]], [[-1.24, -3.47, 2.98, 2.05, 0, 0.25], [-4.47, -5.77, 1.99, -1.17]], [[-4.86, -3.84, 2.72, 0.87, 90, 0.1], [-5.83, -6.66, -3.89, -1.02]]], [[[-3.04, 2.12, 1.02, 1.96, 90, 0.25], [-5.25, 0.85, -0.83, 3.39]], [[0.03, -1.98, 2.08, 0.65, 45, 0.1], [-2.000402, -4.010402, 2.060402, 0.050402]], [[4.18, -1.21, 1.82, 2.07, 45, 0.0], [1.429355, -3.960645, 6.930645, 1.540645]], [[2.08, -3.48, 0.39, 0.88, 30, 0.1], [1.20225, -4.537102, 2.95775, -2.422898]], [[-3.63, 2.62, 0.49, 0.4, -60, 0.0], [-4.22141, 1.995648, -3.03859, 3.244352]], [[4.13, -2.65, 2.98, 1.36, 0, 0.25], [0.9, -4.26, 7.36, -1.04]], [[-4.65, -3.77, 1.53, 2.23, 0, 0.0], [-6.18, -6.0, -3.12, -1.54]], [[1.9, 2.74, 0.94, 2.66, 0, 0.1], [0.86, -0.02, 2.94, 5.5]]]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| case 0 | [-6.766554, -6.186554, -3.073446, -2.493446] | [-6.766554, -6.186554, -3.073446, -2.493446] | Passed |
| case 1 | [-0.098096, -4.70561, 3.778096, -2.63439] | [-0.098096, -4.70561, 3.778096, -2.63439] | Passed |
| case 2 | [-1.76, -8.05, 1.8, -1.67] | [-1.76, -8.05, 1.8, -1.67] | Passed |
| case 3 | [-3.82, 0.71, -1.96, 6.79] | [-3.82, 0.71, -1.96, 6.79] | Passed |
| case 4 | [1.63, -2.84, 2.77, 2.52] | [1.63, -2.84, 2.77, 2.52] | Passed |
| case 5 | [1.8645, 0.197616, 5.9355, 5.542384] | [1.8645, 0.197616, 5.9355, 5.542384] | Passed |
| case 6 | [2.13, 1.1, 4.13, 3.82] | [2.13, 1.1, 4.13, 3.82] | Passed |
| case 7 | [-6.165211, 0.546206, -0.614789, 7.333794] | [-6.165211, 0.546206, -0.614789, 7.333794] | Passed |
SHA-256 / b9f916aff1087e47b232a8b6eacf1aa8756a9ba4a70f45bb60b8acfa7f92ffbc
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.490555+00:00.
Case digest / 5fbda479992c2627cac2e4e3327b30c55ee77eef9e7807f3068857704488e908