FAILURE MAP
← Case archive

FA-87821 / Collision detection broadphase / Open access

Rotated proxy reuses the horizontal formula vertically · case 01

Rotated boxes get square-ish wrong proxies.

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

ROOT CAUSE

ey = c*hx + s*hy.

VERIFIED REPAIR

ey = s*hx + c*hy.

Unsuccessful approach: Using hy in both terms ignores the box width.

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(math.radians(angle))), abs(math.sin(math.radians(angle)))
    ex = c * hx + s * hy
    ey = c * hx + s * 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 = [[[[1.52, 4.0, 2.49, 2.68, 30, 0.25], [-2.226403, 0.184052, 5.266403, 7.815948]], [[-2.36, 0.5, 0.97, 0.7, 315, 0.25], [-3.790868, -0.930868, -0.929132, 1.930868]], [[3.48, 0.63, 1.4, 2.86, 45, 0.1], [0.367725, -2.482275, 6.592275, 3.742275]], [[-2.89, 3.75, 2.94, 0.83, 90, 0.1], [-3.82, 0.71, -1.96, 6.79]], [[1.84, -3.67, 1.72, 0.21, 200, 0.25], [-0.098096, -4.70561, 3.778096, -2.63439]], [[3.25, -1.04, 0.69, 1.83, 0, 0.0], [2.56, -2.87, 3.94, 0.79]], [[-2.14, 0.55, 1.83, 1.69, 30, 0.0], [-4.569826, -1.828583, 0.289826, 2.928583]], [[2.85, -1.15, 0.33, 1.61, 315, 0.1], [1.378213, -2.621787, 4.321787, 0.321787]]], [[[-2.89, 4.67, 2.63, 2.44, 45, 0.1], [-6.575031, 0.984969, 0.795031, 8.355031]], [[2.42, -4.18, 2.84, 0.97, 30, 0.0], [-0.524512, -6.440045, 5.364512, -1.919955]], [[1.95, -1.11, 2.53, 2.16, 315, 0.25], [-1.616331, -4.676331, 5.516331, 2.456331]], [[0.92, 3.63, 1.55, 2.71, 200, 0.1], [-1.563398, 0.453302, 3.403398, 6.806698]], [[-0.03, -1.34, 0.42, 1.39, 45, 0.0], [-1.309863, -2.619863, 1.249863, -0.060137]], [[2.69, 1.17, 0.68, 2.52, 315, 0.25], [0.177258, -1.342742, 5.202742, 3.682742]], [[-3.14, 0.32, 1.05, 2.01, 315, 0.1], [-5.403747, -1.943747, -0.876253, 2.583747]], [[4.14, 1.21, 1.43, 0.9, -60, 0.0], [2.645577, -0.478416, 5.634423, 2.898416]]], [[[4.61, -4.0, 2.59, 1.04, -60, 0.0], [2.414334, -6.763006, 6.805666, -1.236994]], [[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]], [[-4.64, -1.65, 2.86, 1.68, 315, 0.0], [-7.850265, -4.860265, -1.429735, 1.560265]], [[2.35, 0.77, 0.47, 0.36, 30, 0.25], [1.512968, -0.026769, 3.187032, 1.566769]], [[0.69, 0.52, 0.98, 2.31, 45, 0.25], [-1.886381, -2.056381, 3.266381, 3.096381]], [[0.37, -4.27, 0.94, 2.57, 120, 0.0], [-2.325685, -6.369064, 3.065685, -2.170936]], [[-1.48, 2.13, 0.58, 2.08, 315, 0.0], [-3.360904, 0.249096, 0.400904, 4.010904]]], [[[-3.59, 1.71, 2.63, 0.63, 120, 0.0], [-5.450596, -0.882647, -1.729404, 4.302647]], [[2.5, 0.28, 0.5, 1.39, -60, 0.1], [0.946225, -0.948013, 4.053775, 1.508013]], [[4.03, 0.68, 2.73, 2.08, -60, 0.1], [0.763667, -2.824249, 7.296333, 4.184249]], [[2.89, -1.08, 1.78, 2.99, 45, 0.25], [-0.732899, -4.702899, 6.512899, 2.542899]], [[-3.54, -4.59, 0.46, 2.82, 315, 0.0], [-5.85931, -6.90931, -1.22069, -2.27069]], [[1.64, -0.84, 2.93, 1.67, -60, 0.0], [-1.271262, -4.212454, 4.551262, 2.532454]], [[-4.22, -2.52, 1.36, 2.06, 45, 0.25], [-6.888305, -5.188305, -1.551695, 0.148305]], [[4.74, -1.56, 2.8, 2.68, 200, 0.1], [1.092247, -5.136033, 8.387753, 2.016033]]], [[[3.46, 2.58, 2.54, 0.95, 45, 0.1], [0.892197, 0.012197, 6.027803, 5.147803]], [[-0.15, -2.35, 1.64, 0.24, 120, 0.25], [-1.427846, -4.140282, 1.127846, -0.559718]], [[-2.09, -2.58, 2.5, 2.81, 200, 0.1], [-5.500308, -6.175587, 1.320308, 1.015587]], [[0.03, -1.98, 2.08, 0.65, 45, 0.1], [-2.000402, -4.010402, 2.060402, 0.050402]], [[-3.68, 2.12, 1.34, 2.41, 45, 0.1], [-6.43165, -0.63165, -0.92835, 4.87165]], [[4.33, 4.07, 2.75, 0.65, 120, 0.0], [2.392083, 1.36343, 6.267917, 6.77657]], [[-1.73, -1.68, 0.29, 1.98, 30, 0.25], [-3.221147, -3.78973, -0.238853, 0.42973]], [[-0.11, -3.17, 2.19, 2.33, 200, 0.1], [-3.064834, -6.208508, 2.844834, -0.131492]]]]
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[-2.226403, 0.253597, 5.266403, 7.746403][-2.226403, 0.184052, 5.266403, 7.815948]Failed
case 1[-3.790868, -0.930868, -0.929132, 1.930868][-3.790868, -0.930868, -0.929132, 1.930868]Passed
case 2[0.367725, -2.482275, 6.592275, 3.742275][0.367725, -2.482275, 6.592275, 3.742275]Passed
case 3[-3.82, 2.82, -1.96, 4.68][-3.82, 0.71, -1.96, 6.79]Failed
case 4[-0.098096, -5.608096, 3.778096, -1.731904][-0.098096, -4.70561, 3.778096, -2.63439]Failed
case 5[2.56, -1.73, 3.94, -0.35][2.56, -2.87, 3.94, 0.79]Failed
case 6[-4.569826, -1.879826, 0.289826, 2.979826][-4.569826, -1.828583, 0.289826, 2.928583]Failed
case 7[1.378213, -2.621787, 4.321787, 0.321787][1.378213, -2.621787, 4.321787, 0.321787]Passed

SHA-256 / 5411b0e41d0349e15ab7011ea6e33f8bab03c24dffff03b3a8b8e3268235138b

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(math.radians(angle)))
    ex = c * hx + s * hy
    ey = s * hy + 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 = [[[[1.52, 4.0, 2.49, 2.68, 30, 0.25], [-2.226403, 0.184052, 5.266403, 7.815948]], [[-2.36, 0.5, 0.97, 0.7, 315, 0.25], [-3.790868, -0.930868, -0.929132, 1.930868]], [[3.48, 0.63, 1.4, 2.86, 45, 0.1], [0.367725, -2.482275, 6.592275, 3.742275]], [[-2.89, 3.75, 2.94, 0.83, 90, 0.1], [-3.82, 0.71, -1.96, 6.79]], [[1.84, -3.67, 1.72, 0.21, 200, 0.25], [-0.098096, -4.70561, 3.778096, -2.63439]], [[3.25, -1.04, 0.69, 1.83, 0, 0.0], [2.56, -2.87, 3.94, 0.79]], [[-2.14, 0.55, 1.83, 1.69, 30, 0.0], [-4.569826, -1.828583, 0.289826, 2.928583]], [[2.85, -1.15, 0.33, 1.61, 315, 0.1], [1.378213, -2.621787, 4.321787, 0.321787]]], [[[-2.89, 4.67, 2.63, 2.44, 45, 0.1], [-6.575031, 0.984969, 0.795031, 8.355031]], [[2.42, -4.18, 2.84, 0.97, 30, 0.0], [-0.524512, -6.440045, 5.364512, -1.919955]], [[1.95, -1.11, 2.53, 2.16, 315, 0.25], [-1.616331, -4.676331, 5.516331, 2.456331]], [[0.92, 3.63, 1.55, 2.71, 200, 0.1], [-1.563398, 0.453302, 3.403398, 6.806698]], [[-0.03, -1.34, 0.42, 1.39, 45, 0.0], [-1.309863, -2.619863, 1.249863, -0.060137]], [[2.69, 1.17, 0.68, 2.52, 315, 0.25], [0.177258, -1.342742, 5.202742, 3.682742]], [[-3.14, 0.32, 1.05, 2.01, 315, 0.1], [-5.403747, -1.943747, -0.876253, 2.583747]], [[4.14, 1.21, 1.43, 0.9, -60, 0.0], [2.645577, -0.478416, 5.634423, 2.898416]]], [[[4.61, -4.0, 2.59, 1.04, -60, 0.0], [2.414334, -6.763006, 6.805666, -1.236994]], [[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]], [[-4.64, -1.65, 2.86, 1.68, 315, 0.0], [-7.850265, -4.860265, -1.429735, 1.560265]], [[2.35, 0.77, 0.47, 0.36, 30, 0.25], [1.512968, -0.026769, 3.187032, 1.566769]], [[0.69, 0.52, 0.98, 2.31, 45, 0.25], [-1.886381, -2.056381, 3.266381, 3.096381]], [[0.37, -4.27, 0.94, 2.57, 120, 0.0], [-2.325685, -6.369064, 3.065685, -2.170936]], [[-1.48, 2.13, 0.58, 2.08, 315, 0.0], [-3.360904, 0.249096, 0.400904, 4.010904]]], [[[-3.59, 1.71, 2.63, 0.63, 120, 0.0], [-5.450596, -0.882647, -1.729404, 4.302647]], [[2.5, 0.28, 0.5, 1.39, -60, 0.1], [0.946225, -0.948013, 4.053775, 1.508013]], [[4.03, 0.68, 2.73, 2.08, -60, 0.1], [0.763667, -2.824249, 7.296333, 4.184249]], [[2.89, -1.08, 1.78, 2.99, 45, 0.25], [-0.732899, -4.702899, 6.512899, 2.542899]], [[-3.54, -4.59, 0.46, 2.82, 315, 0.0], [-5.85931, -6.90931, -1.22069, -2.27069]], [[1.64, -0.84, 2.93, 1.67, -60, 0.0], [-1.271262, -4.212454, 4.551262, 2.532454]], [[-4.22, -2.52, 1.36, 2.06, 45, 0.25], [-6.888305, -5.188305, -1.551695, 0.148305]], [[4.74, -1.56, 2.8, 2.68, 200, 0.1], [1.092247, -5.136033, 8.387753, 2.016033]]], [[[3.46, 2.58, 2.54, 0.95, 45, 0.1], [0.892197, 0.012197, 6.027803, 5.147803]], [[-0.15, -2.35, 1.64, 0.24, 120, 0.25], [-1.427846, -4.140282, 1.127846, -0.559718]], [[-2.09, -2.58, 2.5, 2.81, 200, 0.1], [-5.500308, -6.175587, 1.320308, 1.015587]], [[0.03, -1.98, 2.08, 0.65, 45, 0.1], [-2.000402, -4.010402, 2.060402, 0.050402]], [[-3.68, 2.12, 1.34, 2.41, 45, 0.1], [-6.43165, -0.63165, -0.92835, 4.87165]], [[4.33, 4.07, 2.75, 0.65, 120, 0.0], [2.392083, 1.36343, 6.267917, 6.77657]], [[-1.73, -1.68, 0.29, 1.98, 30, 0.25], [-3.221147, -3.78973, -0.238853, 0.42973]], [[-0.11, -3.17, 2.19, 2.33, 200, 0.1], [-3.064834, -6.208508, 2.844834, -0.131492]]]]
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[-2.226403, 0.089052, 5.266403, 7.910948][-2.226403, 0.184052, 5.266403, 7.815948]Failed
case 1[-3.790868, -0.739949, -0.929132, 1.739949][-3.790868, -0.930868, -0.929132, 1.930868]Failed
case 2[0.367725, -3.514651, 6.592275, 4.774651][0.367725, -2.482275, 6.592275, 3.742275]Failed
case 3[-3.82, 2.82, -1.96, 4.68][-3.82, 0.71, -1.96, 6.79]Failed
case 4[-0.098096, -4.18916, 3.778096, -3.15084][-0.098096, -4.70561, 3.778096, -2.63439]Failed
case 5[2.56, -2.87, 3.94, 0.79][2.56, -2.87, 3.94, 0.79]Passed
case 6[-4.569826, -1.758583, 0.289826, 2.858583][-4.569826, -1.828583, 0.289826, 2.928583]Failed
case 7[1.378213, -3.526884, 4.321787, 1.226884][1.378213, -2.621787, 4.321787, 0.321787]Failed

SHA-256 / 253139f2902895edea4b9a558c1f261759856406243c17ae8fd06337c9d8d238

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 = [[[[1.52, 4.0, 2.49, 2.68, 30, 0.25], [-2.226403, 0.184052, 5.266403, 7.815948]], [[-2.36, 0.5, 0.97, 0.7, 315, 0.25], [-3.790868, -0.930868, -0.929132, 1.930868]], [[3.48, 0.63, 1.4, 2.86, 45, 0.1], [0.367725, -2.482275, 6.592275, 3.742275]], [[-2.89, 3.75, 2.94, 0.83, 90, 0.1], [-3.82, 0.71, -1.96, 6.79]], [[1.84, -3.67, 1.72, 0.21, 200, 0.25], [-0.098096, -4.70561, 3.778096, -2.63439]], [[3.25, -1.04, 0.69, 1.83, 0, 0.0], [2.56, -2.87, 3.94, 0.79]], [[-2.14, 0.55, 1.83, 1.69, 30, 0.0], [-4.569826, -1.828583, 0.289826, 2.928583]], [[2.85, -1.15, 0.33, 1.61, 315, 0.1], [1.378213, -2.621787, 4.321787, 0.321787]]], [[[-2.89, 4.67, 2.63, 2.44, 45, 0.1], [-6.575031, 0.984969, 0.795031, 8.355031]], [[2.42, -4.18, 2.84, 0.97, 30, 0.0], [-0.524512, -6.440045, 5.364512, -1.919955]], [[1.95, -1.11, 2.53, 2.16, 315, 0.25], [-1.616331, -4.676331, 5.516331, 2.456331]], [[0.92, 3.63, 1.55, 2.71, 200, 0.1], [-1.563398, 0.453302, 3.403398, 6.806698]], [[-0.03, -1.34, 0.42, 1.39, 45, 0.0], [-1.309863, -2.619863, 1.249863, -0.060137]], [[2.69, 1.17, 0.68, 2.52, 315, 0.25], [0.177258, -1.342742, 5.202742, 3.682742]], [[-3.14, 0.32, 1.05, 2.01, 315, 0.1], [-5.403747, -1.943747, -0.876253, 2.583747]], [[4.14, 1.21, 1.43, 0.9, -60, 0.0], [2.645577, -0.478416, 5.634423, 2.898416]]], [[[4.61, -4.0, 2.59, 1.04, -60, 0.0], [2.414334, -6.763006, 6.805666, -1.236994]], [[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]], [[-4.64, -1.65, 2.86, 1.68, 315, 0.0], [-7.850265, -4.860265, -1.429735, 1.560265]], [[2.35, 0.77, 0.47, 0.36, 30, 0.25], [1.512968, -0.026769, 3.187032, 1.566769]], [[0.69, 0.52, 0.98, 2.31, 45, 0.25], [-1.886381, -2.056381, 3.266381, 3.096381]], [[0.37, -4.27, 0.94, 2.57, 120, 0.0], [-2.325685, -6.369064, 3.065685, -2.170936]], [[-1.48, 2.13, 0.58, 2.08, 315, 0.0], [-3.360904, 0.249096, 0.400904, 4.010904]]], [[[-3.59, 1.71, 2.63, 0.63, 120, 0.0], [-5.450596, -0.882647, -1.729404, 4.302647]], [[2.5, 0.28, 0.5, 1.39, -60, 0.1], [0.946225, -0.948013, 4.053775, 1.508013]], [[4.03, 0.68, 2.73, 2.08, -60, 0.1], [0.763667, -2.824249, 7.296333, 4.184249]], [[2.89, -1.08, 1.78, 2.99, 45, 0.25], [-0.732899, -4.702899, 6.512899, 2.542899]], [[-3.54, -4.59, 0.46, 2.82, 315, 0.0], [-5.85931, -6.90931, -1.22069, -2.27069]], [[1.64, -0.84, 2.93, 1.67, -60, 0.0], [-1.271262, -4.212454, 4.551262, 2.532454]], [[-4.22, -2.52, 1.36, 2.06, 45, 0.25], [-6.888305, -5.188305, -1.551695, 0.148305]], [[4.74, -1.56, 2.8, 2.68, 200, 0.1], [1.092247, -5.136033, 8.387753, 2.016033]]], [[[3.46, 2.58, 2.54, 0.95, 45, 0.1], [0.892197, 0.012197, 6.027803, 5.147803]], [[-0.15, -2.35, 1.64, 0.24, 120, 0.25], [-1.427846, -4.140282, 1.127846, -0.559718]], [[-2.09, -2.58, 2.5, 2.81, 200, 0.1], [-5.500308, -6.175587, 1.320308, 1.015587]], [[0.03, -1.98, 2.08, 0.65, 45, 0.1], [-2.000402, -4.010402, 2.060402, 0.050402]], [[-3.68, 2.12, 1.34, 2.41, 45, 0.1], [-6.43165, -0.63165, -0.92835, 4.87165]], [[4.33, 4.07, 2.75, 0.65, 120, 0.0], [2.392083, 1.36343, 6.267917, 6.77657]], [[-1.73, -1.68, 0.29, 1.98, 30, 0.25], [-3.221147, -3.78973, -0.238853, 0.42973]], [[-0.11, -3.17, 2.19, 2.33, 200, 0.1], [-3.064834, -6.208508, 2.844834, -0.131492]]]]
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[-2.226403, 0.184052, 5.266403, 7.815948][-2.226403, 0.184052, 5.266403, 7.815948]Passed
case 1[-3.790868, -0.930868, -0.929132, 1.930868][-3.790868, -0.930868, -0.929132, 1.930868]Passed
case 2[0.367725, -2.482275, 6.592275, 3.742275][0.367725, -2.482275, 6.592275, 3.742275]Passed
case 3[-3.82, 0.71, -1.96, 6.79][-3.82, 0.71, -1.96, 6.79]Passed
case 4[-0.098096, -4.70561, 3.778096, -2.63439][-0.098096, -4.70561, 3.778096, -2.63439]Passed
case 5[2.56, -2.87, 3.94, 0.79][2.56, -2.87, 3.94, 0.79]Passed
case 6[-4.569826, -1.828583, 0.289826, 2.928583][-4.569826, -1.828583, 0.289826, 2.928583]Passed
case 7[1.378213, -2.621787, 4.321787, 0.321787][1.378213, -2.621787, 4.321787, 0.321787]Passed

SHA-256 / fe1f220ae5e8416feeb2a6a87027ad9345ad9b797efd802d1d39325ccab78a9d

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.490564+00:00.

Case digest / 630b2c832f0f36e2e9e8700d334d23afa21a0a4ec4dea24826e6f30bbc0689e3