FAILURE MAP
← Case archive

FA-87221 / Physics integrator stability / Open access

Friction clamps each increment rather than the accumulated tangent impulse · case 01

Sliding contacts accumulate unbounded friction over iterations.

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

ROOT CAUSE

jt = old + clamp(d) instead of clamp(old + d).

VERIFIED REPAIR

Clamp the accumulated tangent impulse into the cone.

Unsuccessful approach: Clamping just the delta and dropping the warm value loses accumulated friction.

Case contract

solve(vn0, vt0, w, mu, iterations, cached, warm): several contact points share one relative normal and tangent velocity with inverse mass w. Warm start each point with warm*cached [jn, jt] and apply it to the velocities. Each iteration visits points in order: friction first (accumulated jt clamped to |jt|<=mu*jn), then normal (accumulated jn clamped >=0). Return [jn list, jt list, vn, vt] rounded to 6.

Why this case matters

Game and robotics physics loops depend on integrator update order, step control and stabilization terms; a wrong decision point turns a stable simulation into drifting or exploding motion.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(vn0, vt0, w, mu, iterations, cached, warm):
    vn, vt = vn0, vt0
    jn = [p[0] * warm for p in cached]
    jt = [p[1] * warm for p in cached]
    for i in range(len(cached)):
        vn += jn[i] * w
        vt += jt[i] * w
    for _ in range(iterations):
        for i in range(len(cached)):
            d = -vt / w
            old = jt[i]
            lim = mu * jn[i]
            jt[i] = old + max(-lim, min(lim, d))
            vt += (jt[i] - old) * w
            d = -vn / w
            old = jn[i]
            jn[i] = max(0.0, old + d)
            vn += (jn[i] - old) * w
    return [[round(a, 6) for a in jn], [round(b, 6) for b in jt], round(vn, 6), round(vt, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[-2.87, 0.462, 0.816, 0.324, 3, [[1.643, -0.652]], 0.8], [[3.517157], [-0.566176], 0.0, 0.0]], [[-3.555, -0.144, 1.579, 0.046, 1, [[0.551, 0.391]], 0.5], [[2.251425], [0.012673], 0.0, -0.123989]], [[0.79, -0.152, 1.496, 0.564, 2, [[1.385, 0.975]], 0.8], [[0.0], [-0.0], 0.79, -0.152]], [[-3.886, 1.26, 0.358, 0.718, 2, [[0.002, 0.092]], 1.0], [[10.854749], [-3.519553], 0.0, 0.0]], [[-2.497, -0.48, 1.087, 0.956, 3, [[1.515, -0.001]], 0.8], [[2.297148], [0.441582], 0.0, 0.0]], [[-2.645, 1.105, 0.531, 0.646, 4, [[1.101, 0.922]], 0.8], [[4.981168], [-2.080979], 0.0, -0.0]], [[-1.289, -0.338, 0.562, 0.525, 2, [[0.186, -0.3], [0.48, -0.774]], 0.8], [[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0]], [[-1.661, 2.802, 0.615, 0.829, 1, [[1.602, 0.798], [1.044, -0.504]], 1.0], [[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977]]], [[[-3.97, -1.524, 0.751, 0.951, 2, [[1.64, -0.149], [1.595, -0.486]], 0.0], [[5.286285, 0.0], [2.029294, -0.0], 0.0, -0.0]], [[-2.979, 2.253, 1.354, 0.901, 1, [[0.338, -0.665], [1.715, -0.942]], 0.8], [[0.828148, 1.372], [-0.24363, -1.236172], 0.0, 0.249348]], [[-2.623, 1.976, 1.325, 0.507, 3, [[0.242, 0.679], [0.105, 0.085]], 0.5], [[1.927123, 0.0525], [-0.977051, -0.026617], 0.0, 0.646139]], [[0.488, 0.647, 0.954, 0.972, 3, [[0.325, 0.033], [1.245, -0.259], [1.464, 0.427]], 0.0], [[0.0, 0.0, 0.0], [-0.0, -0.0, -0.0], 0.488, 0.647]], [[-3.044, 2.315, 1.439, 0.741, 2, [[1.446, 0.156], [1.096, 0.477]], 0.0], [[2.115358, 0.0], [-1.56748, -0.0], 0.0, 0.059396]], [[-0.085, -2.818, 1.518, 0.65, 2, [[1.041, -0.373]], 0.8], [[0.055995], [0.036397], 0.0, -2.76275]], [[-3.058, 1.796, 1.383, 0.609, 4, [[0.742, -0.639], [1.013, -0.169], [0.53, -1.0]], 0.8], [[0.976735, 0.8104, 0.424], [-0.594832, -0.445578, -0.258216], -0.0, -0.0]], [[-3.108, 2.921, 1.142, 0.923, 4, [[0.023, -0.195]], 0.8], [[2.721541], [-2.511982], 0.0, 0.052316]]], [[[-3.951, 1.582, 0.733, 0.64, 3, [[1.42, 0.348], [1.975, -0.599], [1.407, -0.117]], 0.0], [[5.390177, 0.0, 0.0], [-2.158254, -0.0, -0.0], 0.0, 0.0]], [[-2.719, 0.698, 1.072, 0.997, 3, [[0.465, 0.673], [0.842, 0.478], [1.551, -0.546]], 0.0], [[2.536381, 0.0, 0.0], [-0.651119, -0.0, -0.0], 0.0, 0.0]], [[0.155, -2.126, 1.449, 0.01, 2, [[1.311, 0.067]], 0.0], [[0.0], [-0.0], 0.155, -2.126]], [[-2.82, 1.429, 1.478, 0.114, 2, [[0.588, -0.945], [0.049, -0.934], [1.67, 0.896]], 0.0], [[1.907984, 0.0, 0.0], [-0.21751, -0.0, -0.0], 0.0, 1.10752]], [[-0.278, 0.613, 0.658, 0.929, 2, [[0.995, -0.862], [1.959, -0.61], [0.718, -0.073]], 0.8], [[0.0, 0.0, 0.422492], [-0.0, -0.0, -0.392495], -0.0, 0.354738]], [[-3.957, 2.421, 0.732, 0.724, 3, [[0.416, 0.146]], 1.0], [[5.405738], [-3.307377], -0.0, 0.0]], [[-2.977, 1.342, 1.449, 0.703, 2, [[0.4, 0.057], [1.071, -0.664], [0.662, 0.869]], 0.5], [[1.18802, 0.5355, 0.331], [-0.317006, -0.376456, -0.232693], -0.0, 0.0]], [[0.781, -0.836, 1.143, 0.549, 1, [[1.624, -0.843], [0.854, 0.294]], 0.5], [[0.0, 0.0], [0.445788, 0.234423], 0.781, -0.058519]]], [[[-1.685, -0.874, 0.355, 0.599, 4, [[1.955, -0.346]], 0.8], [[4.746479], [2.461972], 0.0, -0.0]], [[-3.004, 2.286, 1.734, 0.557, 1, [[0.793, -0.701], [1.467, -0.694]], 0.5], [[0.998911, 0.7335], [-0.220851, -0.40856], 0.0, 1.194603]], [[1.0, 0.58, 0.89, 0.999, 1, [[1.981, 0.778], [1.314, -0.48], [1.399, 0.963]], 0.8], [[0.0, 0.0, 0.0], [-1.038085, -0.384, 0.7704], 1.0, 0.0]], [[-3.749, -1.521, 1.867, 0.93, 3, [[0.955, -0.797], [1.996, 0.952], [1.038, -0.818]], 1.0], [[0.0, 0.970034, 1.038], [-0.0, 0.902132, -0.087456], 0.0, 0.0]], [[-1.373, 1.897, 0.773, 0.632, 2, [[0.114, 0.069], [0.645, 0.663]], 0.0], [[1.776197, 0.0], [-1.122556, -0.0], 0.0, 1.029264]], [[-0.823, 1.266, 0.643, 0.097, 1, [[0.928, 0.05], [0.082, 0.565]], 0.0], [[1.279938, 0.0], [-0.0, -0.0], 0.0, 1.266]], [[-3.691, -1.039, 0.872, 0.44, 1, [[0.026, -0.018]], 0.5], [[4.232798], [0.00572], 0.0, -1.034012]], [[-2.744, -1.245, 1.139, 0.752, 4, [[0.428, -0.831], [0.066, 0.506], [1.83, -0.485]], 0.0], [[2.409131, 0.0, 0.0], [1.093064, -0.0, -0.0], 0.0, 0.0]]], [[[-3.549, 0.408, 1.966, 0.769, 4, [[0.938, -0.821], [1.647, 0.767], [1.404, -0.412]], 0.5], [[0.279688, 0.8235, 0.702], [-0.21508, 0.213552, -0.206], 0.0, 0.0]], [[-1.915, -0.673, 1.751, 0.041, 1, [[0.226, -0.299]], 0.8], [[1.093661], [0.007413], -0.0, -0.66002]], [[-0.517, 1.185, 0.479, 0.385, 3, [[0.577, 0.74], [0.187, -0.121]], 0.0], [[1.079332, 0.0], [-0.415543, -0.0], 0.0, 0.985955]], [[-2.69, 0.712, 1.143, 0.785, 2, [[1.714, -0.723], [1.534, -0.36]], 1.0], [[0.819456, 1.534], [-0.262922, -0.36], 0.0, 0.0]], [[0.963, 1.55, 1.235, 1.0, 2, [[1.265, 0.517], [1.674, -0.353], [1.159, -0.86]], 0.5], [[0.0, 0.0, 0.0], [-0.0, -0.0, -0.0], 0.963, 1.55]], [[-3.372, -0.912, 0.24, 0.634, 4, [[1.541, -0.398]], 0.0], [[14.05], [3.8], 0.0, 0.0]], [[0.977, -0.644, 0.452, 0.107, 1, [[0.272, -0.393], [1.275, -0.698]], 0.0], [[0.0, 0.0], [-0.0, -0.0], 0.977, -0.644]], [[-3.781, 1.225, 1.132, 0.558, 4, [[0.356, 0.879], [1.1, -0.78]], 0.5], [[2.790106, 0.55], [-0.775255, -0.3069], 0.0, 0.0]]]]
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[[3.517157], [-0.566176], 0.0, 0.0][[3.517157], [-0.566176], 0.0, 0.0]Passed
case 1[[2.251425], [0.182827], 0.0, 0.144684][[2.251425], [0.012673], 0.0, -0.123989]Failed
case 2[[0.0], [0.155088], 0.79, 0.080012][[0.0], [-0.0], 0.79, -0.152]Failed
case 3[[10.854749], [-3.519553], 0.0, 0.0][[10.854749], [-3.519553], 0.0, 0.0]Passed
case 4[[2.297148], [0.441582], 0.0, 0.0][[2.297148], [0.441582], 0.0, 0.0]Passed
case 5[[4.981168], [-2.080979], 0.0, 0.0][[4.981168], [-2.080979], 0.0, -0.0]Passed
case 6[[1.909594, 0.384], [0.840657, -0.239234], 0.0, 0.0][[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0]Failed
case 7[[1.656813, 1.044], [-0.530058, -1.369476], -0.0, 1.633787][[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977]Failed

SHA-256 / 02c389b27d36cd55402237bd6acbda8e42ed91b7fc3ba4b54fd011790dbfd09e

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(vn0, vt0, w, mu, iterations, cached, warm):
    vn, vt = vn0, vt0
    jn = [p[0] * warm for p in cached]
    jt = [p[1] * warm for p in cached]
    for i in range(len(cached)):
        vn += jn[i] * w
        vt += jt[i] * w
    for _ in range(iterations):
        for i in range(len(cached)):
            d = -vt / w
            old = jt[i]
            lim = mu * jn[i]
            jt[i] = max(-lim, min(lim, d))
            vt += (jt[i] - old) * w
            d = -vn / w
            old = jn[i]
            jn[i] = max(0.0, old + d)
            vn += (jn[i] - old) * w
    return [[round(a, 6) for a in jn], [round(b, 6) for b in jt], round(vn, 6), round(vt, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[-2.87, 0.462, 0.816, 0.324, 3, [[1.643, -0.652]], 0.8], [[3.517157], [-0.566176], 0.0, 0.0]], [[-3.555, -0.144, 1.579, 0.046, 1, [[0.551, 0.391]], 0.5], [[2.251425], [0.012673], 0.0, -0.123989]], [[0.79, -0.152, 1.496, 0.564, 2, [[1.385, 0.975]], 0.8], [[0.0], [-0.0], 0.79, -0.152]], [[-3.886, 1.26, 0.358, 0.718, 2, [[0.002, 0.092]], 1.0], [[10.854749], [-3.519553], 0.0, 0.0]], [[-2.497, -0.48, 1.087, 0.956, 3, [[1.515, -0.001]], 0.8], [[2.297148], [0.441582], 0.0, 0.0]], [[-2.645, 1.105, 0.531, 0.646, 4, [[1.101, 0.922]], 0.8], [[4.981168], [-2.080979], 0.0, -0.0]], [[-1.289, -0.338, 0.562, 0.525, 2, [[0.186, -0.3], [0.48, -0.774]], 0.8], [[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0]], [[-1.661, 2.802, 0.615, 0.829, 1, [[1.602, 0.798], [1.044, -0.504]], 1.0], [[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977]]], [[[-3.97, -1.524, 0.751, 0.951, 2, [[1.64, -0.149], [1.595, -0.486]], 0.0], [[5.286285, 0.0], [2.029294, -0.0], 0.0, -0.0]], [[-2.979, 2.253, 1.354, 0.901, 1, [[0.338, -0.665], [1.715, -0.942]], 0.8], [[0.828148, 1.372], [-0.24363, -1.236172], 0.0, 0.249348]], [[-2.623, 1.976, 1.325, 0.507, 3, [[0.242, 0.679], [0.105, 0.085]], 0.5], [[1.927123, 0.0525], [-0.977051, -0.026617], 0.0, 0.646139]], [[0.488, 0.647, 0.954, 0.972, 3, [[0.325, 0.033], [1.245, -0.259], [1.464, 0.427]], 0.0], [[0.0, 0.0, 0.0], [-0.0, -0.0, -0.0], 0.488, 0.647]], [[-3.044, 2.315, 1.439, 0.741, 2, [[1.446, 0.156], [1.096, 0.477]], 0.0], [[2.115358, 0.0], [-1.56748, -0.0], 0.0, 0.059396]], [[-0.085, -2.818, 1.518, 0.65, 2, [[1.041, -0.373]], 0.8], [[0.055995], [0.036397], 0.0, -2.76275]], [[-3.058, 1.796, 1.383, 0.609, 4, [[0.742, -0.639], [1.013, -0.169], [0.53, -1.0]], 0.8], [[0.976735, 0.8104, 0.424], [-0.594832, -0.445578, -0.258216], -0.0, -0.0]], [[-3.108, 2.921, 1.142, 0.923, 4, [[0.023, -0.195]], 0.8], [[2.721541], [-2.511982], 0.0, 0.052316]]], [[[-3.951, 1.582, 0.733, 0.64, 3, [[1.42, 0.348], [1.975, -0.599], [1.407, -0.117]], 0.0], [[5.390177, 0.0, 0.0], [-2.158254, -0.0, -0.0], 0.0, 0.0]], [[-2.719, 0.698, 1.072, 0.997, 3, [[0.465, 0.673], [0.842, 0.478], [1.551, -0.546]], 0.0], [[2.536381, 0.0, 0.0], [-0.651119, -0.0, -0.0], 0.0, 0.0]], [[0.155, -2.126, 1.449, 0.01, 2, [[1.311, 0.067]], 0.0], [[0.0], [-0.0], 0.155, -2.126]], [[-2.82, 1.429, 1.478, 0.114, 2, [[0.588, -0.945], [0.049, -0.934], [1.67, 0.896]], 0.0], [[1.907984, 0.0, 0.0], [-0.21751, -0.0, -0.0], 0.0, 1.10752]], [[-0.278, 0.613, 0.658, 0.929, 2, [[0.995, -0.862], [1.959, -0.61], [0.718, -0.073]], 0.8], [[0.0, 0.0, 0.422492], [-0.0, -0.0, -0.392495], -0.0, 0.354738]], [[-3.957, 2.421, 0.732, 0.724, 3, [[0.416, 0.146]], 1.0], [[5.405738], [-3.307377], -0.0, 0.0]], [[-2.977, 1.342, 1.449, 0.703, 2, [[0.4, 0.057], [1.071, -0.664], [0.662, 0.869]], 0.5], [[1.18802, 0.5355, 0.331], [-0.317006, -0.376456, -0.232693], -0.0, 0.0]], [[0.781, -0.836, 1.143, 0.549, 1, [[1.624, -0.843], [0.854, 0.294]], 0.5], [[0.0, 0.0], [0.445788, 0.234423], 0.781, -0.058519]]], [[[-1.685, -0.874, 0.355, 0.599, 4, [[1.955, -0.346]], 0.8], [[4.746479], [2.461972], 0.0, -0.0]], [[-3.004, 2.286, 1.734, 0.557, 1, [[0.793, -0.701], [1.467, -0.694]], 0.5], [[0.998911, 0.7335], [-0.220851, -0.40856], 0.0, 1.194603]], [[1.0, 0.58, 0.89, 0.999, 1, [[1.981, 0.778], [1.314, -0.48], [1.399, 0.963]], 0.8], [[0.0, 0.0, 0.0], [-1.038085, -0.384, 0.7704], 1.0, 0.0]], [[-3.749, -1.521, 1.867, 0.93, 3, [[0.955, -0.797], [1.996, 0.952], [1.038, -0.818]], 1.0], [[0.0, 0.970034, 1.038], [-0.0, 0.902132, -0.087456], 0.0, 0.0]], [[-1.373, 1.897, 0.773, 0.632, 2, [[0.114, 0.069], [0.645, 0.663]], 0.0], [[1.776197, 0.0], [-1.122556, -0.0], 0.0, 1.029264]], [[-0.823, 1.266, 0.643, 0.097, 1, [[0.928, 0.05], [0.082, 0.565]], 0.0], [[1.279938, 0.0], [-0.0, -0.0], 0.0, 1.266]], [[-3.691, -1.039, 0.872, 0.44, 1, [[0.026, -0.018]], 0.5], [[4.232798], [0.00572], 0.0, -1.034012]], [[-2.744, -1.245, 1.139, 0.752, 4, [[0.428, -0.831], [0.066, 0.506], [1.83, -0.485]], 0.0], [[2.409131, 0.0, 0.0], [1.093064, -0.0, -0.0], 0.0, 0.0]]], [[[-3.549, 0.408, 1.966, 0.769, 4, [[0.938, -0.821], [1.647, 0.767], [1.404, -0.412]], 0.5], [[0.279688, 0.8235, 0.702], [-0.21508, 0.213552, -0.206], 0.0, 0.0]], [[-1.915, -0.673, 1.751, 0.041, 1, [[0.226, -0.299]], 0.8], [[1.093661], [0.007413], -0.0, -0.66002]], [[-0.517, 1.185, 0.479, 0.385, 3, [[0.577, 0.74], [0.187, -0.121]], 0.0], [[1.079332, 0.0], [-0.415543, -0.0], 0.0, 0.985955]], [[-2.69, 0.712, 1.143, 0.785, 2, [[1.714, -0.723], [1.534, -0.36]], 1.0], [[0.819456, 1.534], [-0.262922, -0.36], 0.0, 0.0]], [[0.963, 1.55, 1.235, 1.0, 2, [[1.265, 0.517], [1.674, -0.353], [1.159, -0.86]], 0.5], [[0.0, 0.0, 0.0], [-0.0, -0.0, -0.0], 0.963, 1.55]], [[-3.372, -0.912, 0.24, 0.634, 4, [[1.541, -0.398]], 0.0], [[14.05], [3.8], 0.0, 0.0]], [[0.977, -0.644, 0.452, 0.107, 1, [[0.272, -0.393], [1.275, -0.698]], 0.0], [[0.0, 0.0], [-0.0, -0.0], 0.977, -0.644]], [[-3.781, 1.225, 1.132, 0.558, 4, [[0.356, 0.879], [1.1, -0.78]], 0.5], [[2.790106, 0.55], [-0.775255, -0.3069], 0.0, 0.0]]]]
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[[3.517157], [-0.044576], 0.0, 0.425626][[3.517157], [-0.566176], 0.0, 0.0]Failed
case 1[[2.251425], [-0.012673], 0.0, -0.164011][[2.251425], [0.012673], 0.0, -0.123989]Failed
case 2[[0.0], [-0.0], 0.79, -0.152][[0.0], [-0.0], 0.79, -0.152]Passed
case 3[[10.854749], [-3.518117], 0.0, 0.000514][[10.854749], [-3.519553], 0.0, 0.0]Failed
case 4[[2.297148], [0.442382], 0.0, 0.00087][[2.297148], [0.441582], 0.0, 0.0]Failed
case 5[[4.981168], [-1.511982], 0.0, 0.302137][[4.981168], [-2.080979], 0.0, -0.0]Failed
case 6[[1.909594, 0.384], [0.321703, 0.07812], 0.0, -0.113299][[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0]Failed
case 7[[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977][[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977]Passed

SHA-256 / 34caa3ce913d7a917ee9151400db5de9df118cc9dc1699a1182481bd5696a631

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(vn0, vt0, w, mu, iterations, cached, warm):
    vn, vt = vn0, vt0
    jn = [p[0] * warm for p in cached]
    jt = [p[1] * warm for p in cached]
    for i in range(len(cached)):
        vn += jn[i] * w
        vt += jt[i] * w
    for _ in range(iterations):
        for i in range(len(cached)):
            d = -vt / w
            old = jt[i]
            lim = mu * jn[i]
            jt[i] = max(-lim, min(lim, old + d))
            vt += (jt[i] - old) * w
            d = -vn / w
            old = jn[i]
            jn[i] = max(0.0, old + d)
            vn += (jn[i] - old) * w
    return [[round(a, 6) for a in jn], [round(b, 6) for b in jt], round(vn, 6), round(vt, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[-2.87, 0.462, 0.816, 0.324, 3, [[1.643, -0.652]], 0.8], [[3.517157], [-0.566176], 0.0, 0.0]], [[-3.555, -0.144, 1.579, 0.046, 1, [[0.551, 0.391]], 0.5], [[2.251425], [0.012673], 0.0, -0.123989]], [[0.79, -0.152, 1.496, 0.564, 2, [[1.385, 0.975]], 0.8], [[0.0], [-0.0], 0.79, -0.152]], [[-3.886, 1.26, 0.358, 0.718, 2, [[0.002, 0.092]], 1.0], [[10.854749], [-3.519553], 0.0, 0.0]], [[-2.497, -0.48, 1.087, 0.956, 3, [[1.515, -0.001]], 0.8], [[2.297148], [0.441582], 0.0, 0.0]], [[-2.645, 1.105, 0.531, 0.646, 4, [[1.101, 0.922]], 0.8], [[4.981168], [-2.080979], 0.0, -0.0]], [[-1.289, -0.338, 0.562, 0.525, 2, [[0.186, -0.3], [0.48, -0.774]], 0.8], [[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0]], [[-1.661, 2.802, 0.615, 0.829, 1, [[1.602, 0.798], [1.044, -0.504]], 1.0], [[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977]]], [[[-3.97, -1.524, 0.751, 0.951, 2, [[1.64, -0.149], [1.595, -0.486]], 0.0], [[5.286285, 0.0], [2.029294, -0.0], 0.0, -0.0]], [[-2.979, 2.253, 1.354, 0.901, 1, [[0.338, -0.665], [1.715, -0.942]], 0.8], [[0.828148, 1.372], [-0.24363, -1.236172], 0.0, 0.249348]], [[-2.623, 1.976, 1.325, 0.507, 3, [[0.242, 0.679], [0.105, 0.085]], 0.5], [[1.927123, 0.0525], [-0.977051, -0.026617], 0.0, 0.646139]], [[0.488, 0.647, 0.954, 0.972, 3, [[0.325, 0.033], [1.245, -0.259], [1.464, 0.427]], 0.0], [[0.0, 0.0, 0.0], [-0.0, -0.0, -0.0], 0.488, 0.647]], [[-3.044, 2.315, 1.439, 0.741, 2, [[1.446, 0.156], [1.096, 0.477]], 0.0], [[2.115358, 0.0], [-1.56748, -0.0], 0.0, 0.059396]], [[-0.085, -2.818, 1.518, 0.65, 2, [[1.041, -0.373]], 0.8], [[0.055995], [0.036397], 0.0, -2.76275]], [[-3.058, 1.796, 1.383, 0.609, 4, [[0.742, -0.639], [1.013, -0.169], [0.53, -1.0]], 0.8], [[0.976735, 0.8104, 0.424], [-0.594832, -0.445578, -0.258216], -0.0, -0.0]], [[-3.108, 2.921, 1.142, 0.923, 4, [[0.023, -0.195]], 0.8], [[2.721541], [-2.511982], 0.0, 0.052316]]], [[[-3.951, 1.582, 0.733, 0.64, 3, [[1.42, 0.348], [1.975, -0.599], [1.407, -0.117]], 0.0], [[5.390177, 0.0, 0.0], [-2.158254, -0.0, -0.0], 0.0, 0.0]], [[-2.719, 0.698, 1.072, 0.997, 3, [[0.465, 0.673], [0.842, 0.478], [1.551, -0.546]], 0.0], [[2.536381, 0.0, 0.0], [-0.651119, -0.0, -0.0], 0.0, 0.0]], [[0.155, -2.126, 1.449, 0.01, 2, [[1.311, 0.067]], 0.0], [[0.0], [-0.0], 0.155, -2.126]], [[-2.82, 1.429, 1.478, 0.114, 2, [[0.588, -0.945], [0.049, -0.934], [1.67, 0.896]], 0.0], [[1.907984, 0.0, 0.0], [-0.21751, -0.0, -0.0], 0.0, 1.10752]], [[-0.278, 0.613, 0.658, 0.929, 2, [[0.995, -0.862], [1.959, -0.61], [0.718, -0.073]], 0.8], [[0.0, 0.0, 0.422492], [-0.0, -0.0, -0.392495], -0.0, 0.354738]], [[-3.957, 2.421, 0.732, 0.724, 3, [[0.416, 0.146]], 1.0], [[5.405738], [-3.307377], -0.0, 0.0]], [[-2.977, 1.342, 1.449, 0.703, 2, [[0.4, 0.057], [1.071, -0.664], [0.662, 0.869]], 0.5], [[1.18802, 0.5355, 0.331], [-0.317006, -0.376456, -0.232693], -0.0, 0.0]], [[0.781, -0.836, 1.143, 0.549, 1, [[1.624, -0.843], [0.854, 0.294]], 0.5], [[0.0, 0.0], [0.445788, 0.234423], 0.781, -0.058519]]], [[[-1.685, -0.874, 0.355, 0.599, 4, [[1.955, -0.346]], 0.8], [[4.746479], [2.461972], 0.0, -0.0]], [[-3.004, 2.286, 1.734, 0.557, 1, [[0.793, -0.701], [1.467, -0.694]], 0.5], [[0.998911, 0.7335], [-0.220851, -0.40856], 0.0, 1.194603]], [[1.0, 0.58, 0.89, 0.999, 1, [[1.981, 0.778], [1.314, -0.48], [1.399, 0.963]], 0.8], [[0.0, 0.0, 0.0], [-1.038085, -0.384, 0.7704], 1.0, 0.0]], [[-3.749, -1.521, 1.867, 0.93, 3, [[0.955, -0.797], [1.996, 0.952], [1.038, -0.818]], 1.0], [[0.0, 0.970034, 1.038], [-0.0, 0.902132, -0.087456], 0.0, 0.0]], [[-1.373, 1.897, 0.773, 0.632, 2, [[0.114, 0.069], [0.645, 0.663]], 0.0], [[1.776197, 0.0], [-1.122556, -0.0], 0.0, 1.029264]], [[-0.823, 1.266, 0.643, 0.097, 1, [[0.928, 0.05], [0.082, 0.565]], 0.0], [[1.279938, 0.0], [-0.0, -0.0], 0.0, 1.266]], [[-3.691, -1.039, 0.872, 0.44, 1, [[0.026, -0.018]], 0.5], [[4.232798], [0.00572], 0.0, -1.034012]], [[-2.744, -1.245, 1.139, 0.752, 4, [[0.428, -0.831], [0.066, 0.506], [1.83, -0.485]], 0.0], [[2.409131, 0.0, 0.0], [1.093064, -0.0, -0.0], 0.0, 0.0]]], [[[-3.549, 0.408, 1.966, 0.769, 4, [[0.938, -0.821], [1.647, 0.767], [1.404, -0.412]], 0.5], [[0.279688, 0.8235, 0.702], [-0.21508, 0.213552, -0.206], 0.0, 0.0]], [[-1.915, -0.673, 1.751, 0.041, 1, [[0.226, -0.299]], 0.8], [[1.093661], [0.007413], -0.0, -0.66002]], [[-0.517, 1.185, 0.479, 0.385, 3, [[0.577, 0.74], [0.187, -0.121]], 0.0], [[1.079332, 0.0], [-0.415543, -0.0], 0.0, 0.985955]], [[-2.69, 0.712, 1.143, 0.785, 2, [[1.714, -0.723], [1.534, -0.36]], 1.0], [[0.819456, 1.534], [-0.262922, -0.36], 0.0, 0.0]], [[0.963, 1.55, 1.235, 1.0, 2, [[1.265, 0.517], [1.674, -0.353], [1.159, -0.86]], 0.5], [[0.0, 0.0, 0.0], [-0.0, -0.0, -0.0], 0.963, 1.55]], [[-3.372, -0.912, 0.24, 0.634, 4, [[1.541, -0.398]], 0.0], [[14.05], [3.8], 0.0, 0.0]], [[0.977, -0.644, 0.452, 0.107, 1, [[0.272, -0.393], [1.275, -0.698]], 0.0], [[0.0, 0.0], [-0.0, -0.0], 0.977, -0.644]], [[-3.781, 1.225, 1.132, 0.558, 4, [[0.356, 0.879], [1.1, -0.78]], 0.5], [[2.790106, 0.55], [-0.775255, -0.3069], 0.0, 0.0]]]]
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[[3.517157], [-0.566176], 0.0, 0.0][[3.517157], [-0.566176], 0.0, 0.0]Passed
case 1[[2.251425], [0.012673], 0.0, -0.123989][[2.251425], [0.012673], 0.0, -0.123989]Passed
case 2[[0.0], [-0.0], 0.79, -0.152][[0.0], [-0.0], 0.79, -0.152]Passed
case 3[[10.854749], [-3.519553], 0.0, 0.0][[10.854749], [-3.519553], 0.0, 0.0]Passed
case 4[[2.297148], [0.441582], 0.0, 0.0][[2.297148], [0.441582], 0.0, 0.0]Passed
case 5[[4.981168], [-2.080979], 0.0, -0.0][[4.981168], [-2.080979], 0.0, -0.0]Passed
case 6[[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0][[1.909594, 0.384], [0.399823, 0.2016], 0.0, 0.0]Passed
case 7[[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977][[1.656813, 1.044], [-1.328058, -0.865476], -0.0, 1.452977]Passed

SHA-256 / 8fa022654dbcbae7c5133b0b56727d76d55a04a6974a8cfc32dbb3d2ab84c6e2

Verification & scope

A deterministic bounded teaching model with stipulated toy conventions and rounded float output; not a production physics engine or a proof of numerical stability. 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:50:56.651712+00:00.

Case digest / b3ed7acd7ca8b6c21b54667d6d2bee440fcc78e3716ab747d58656c5d85c0de6