FAILURE MAP
← Case archive

FA-87116 / Physics integrator stability / Open access

Implicit damping term is not divided by mass · case 01

Damping strength scales wrongly with body mass.

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

ROOT CAUSE

The denominator uses dt*c instead of dt*c/m.

VERIFIED REPAIR

Normalize damping by mass in the implicit denominator.

Unsuccessful approach: Multiplying by mass inverts the dependency.

Case contract

solve(x0, v0, k, m, c, dt, steps): backward Euler for x'=v, m v'=-k x-c v in closed form v1=(v-dt*k*x/m)/(1+dt*c/m+dt^2*k/m), x1=x+dt*v1. Energy E=0.5 m v^2+0.5 k x^2; report the peak E_n/E_0 including step 0 (0 when E_0=0). Return [x, v, peak] 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(x0, v0, k, m, c, dt, steps):
    x, v = x0, v0
    def energy(p, q):
        return 0.5 * m * q * q + 0.5 * k * p * p
    e0 = energy(x, v)
    peak = 1.0 if e0 > 0 else 0.0
    for _ in range(steps):
        v = (v - dt * k * x / m) / (1 + dt * c + dt * dt * k / m)
        x = x + dt * v
        if e0 > 0:
            peak = max(peak, energy(x, v) / e0)
    return [round(x, 6), round(v, 6), round(peak, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.298, 1.963, 89.387, 2.381, 2.482, 0.1, 4], [0.067482, -1.242186, 1.0]], [[0.191, 1.945, 34.85, 2.123, 1.81, 0.5, 2], [0.064229, -0.321773, 1.0]], [[-1.795, -0.563, 97.971, 1.205, 2.366, 0.01, 5], [-1.618428, 6.062195, 1.0]], [[1.095, -1.653, 34.425, 1.885, 0.995, 0.01, 2], [1.056379, -2.022152, 1.0]], [[1.041, -0.449, 66.736, 0.709, 0.609, 0.05, 1], [0.831798, -4.184036, 1.0]], [[1.633, -0.225, 42.999, 2.61, 2.771, 0.5, 1], [0.422579, -2.420843, 1.0]], [[0.132, -0.838, 0.072, 0.412, 0.208, 0.01, 1], [0.12366, -0.834006, 1.0]]], [[[0.194, -0.92, 25.988, 2.712, 0.886, 0.1, 8], [-0.209628, 0.163927, 1.0]], [[0.058, 1.224, 42.535, 1.517, 0.328, 0.5, 5], [-0.000101, 0.006743, 1.0]], [[0.666, 1.74, 57.877, 2.068, 2.127, 0.1, 1], [0.657037, -0.089628, 1.0]], [[-0.368, -0.287, 23.934, 2.087, 1.05, 0.05, 2], [-0.364916, 0.133775, 1.0]], [[0.326, 0.466, 61.522, 2.623, 2.291, 0.01, 3], [0.335138, 0.223966, 1.0]], [[-0.519, -0.968, 62.61, 2.039, 0.152, 0.1, 6], [0.217976, 0.56902, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[1.416, 1.173, 43.299, 0.754, 2.467, 0.1, 1], [1.050042, -3.659575, 1.0]]], [[[-1.401, 0.585, 79.1, 0.893, 2.35, 0.5, 10], [-0.0, -0.0, 1.0]], [[-1.724, -0.957, 58.152, 0.412, 2.15, 0.01, 9], [-0.983354, 13.094621, 1.0]], [[1.914, 1.215, 50.88, 2.233, 2.185, 0.05, 7], [0.287159, -6.563774, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-1.199, 0.933, 73.961, 1.405, 2.45, 0.01, 5], [-1.067469, 3.680079, 1.0]], [[1.064, 1.69, 52.451, 0.532, 2.034, 0.5, 1], [0.143071, -1.841857, 1.0]], [[1.383, 1.181, 88.719, 2.586, 1.037, 0.01, 9], [1.278154, -2.961826, 1.0]], [[-0.458, -1.475, 76.199, 2.36, 0.385, 0.1, 1], [-0.457718, 0.002821, 1.0]]], [[[0.777, 1.994, 57.363, 2.434, 0.585, 0.01, 3], [0.82519, 1.409422, 1.0]], [[1.857, -0.996, 11.568, 1.719, 0.343, 0.5, 9], [-0.005974, -0.045732, 1.0]], [[-0.661, 0.376, 66.073, 2.358, 0.785, 0.01, 2], [-0.648053, 0.736934, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.421, 0.887, 67.079, 0.988, 1.223, 0.05, 9], [-0.17349, 0.037384, 1.0]], [[-0.828, -0.185, 67.12, 0.517, 2.786, 0.5, 10], [-0.0, -0.0, 1.0]], [[-1.587, 0.039, 21.152, 0.464, 2.072, 0.01, 12], [-1.152948, 5.796668, 1.0]], [[0.939, -0.391, 80.372, 0.897, 0.322, 0.5, 12], [-0.0, 0.0, 1.0]]], [[[1.48, -1.5, 47.647, 0.462, 0.24, 0.5, 7], [-1.4e-05, 2e-05, 1.0]], [[-1.472, -1.496, 89.833, 2.285, 2.932, 0.5, 13], [0.0, 1e-06, 1.0]], [[0.318, -1.09, 68.068, 1.546, 0.961, 0.5, 5], [0.000624, -0.001624, 1.0]], [[0.537, 1.491, 17.106, 2.255, 2.958, 0.05, 7], [0.657535, -0.41846, 1.0]], [[0.544, 1.609, 65.688, 1.374, 0.719, 0.01, 10], [0.552951, -1.128422, 1.0]], [[1.452, 1.003, 62.635, 0.47, 0.091, 0.01, 7], [1.023334, -10.790614, 1.0]], [[0.539, 1.387, 93.276, 0.984, 1.857, 0.5, 7], [-4e-06, -4.4e-05, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 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[0.0, 0.0, 0.0][0.0, 0.0, 0.0]Passed
case 1[0.093369, -1.079888, 1.0][0.067482, -1.242186, 1.0]Failed
case 2[0.075732, -0.29333, 1.0][0.064229, -0.321773, 1.0]Failed
case 3[-1.619925, 5.997059, 1.0][-1.618428, 6.062195, 1.0]Failed
case 4[1.056642, -2.004434, 1.0][1.056379, -2.022152, 1.0]Failed
case 5[0.829733, -4.225348, 1.0][0.831798, -4.184036, 1.0]Failed
case 6[0.581629, -2.102741, 1.0][0.422579, -2.420843, 1.0]Failed
case 7[0.123635, -0.836476, 1.0][0.12366, -0.834006, 1.0]Failed

SHA-256 / 2cda8318c616c04e595d8dd8832762a2ea2dea03acd2b8f5d3dea5861338e7cd

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x0, v0, k, m, c, dt, steps):
    x, v = x0, v0
    def energy(p, q):
        return 0.5 * m * q * q + 0.5 * k * p * p
    e0 = energy(x, v)
    peak = 1.0 if e0 > 0 else 0.0
    for _ in range(steps):
        v = (v - dt * k * x / m) / (1 + dt * c * m + dt * dt * k / m)
        x = x + dt * v
        if e0 > 0:
            peak = max(peak, energy(x, v) / e0)
    return [round(x, 6), round(v, 6), round(peak, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.298, 1.963, 89.387, 2.381, 2.482, 0.1, 4], [0.067482, -1.242186, 1.0]], [[0.191, 1.945, 34.85, 2.123, 1.81, 0.5, 2], [0.064229, -0.321773, 1.0]], [[-1.795, -0.563, 97.971, 1.205, 2.366, 0.01, 5], [-1.618428, 6.062195, 1.0]], [[1.095, -1.653, 34.425, 1.885, 0.995, 0.01, 2], [1.056379, -2.022152, 1.0]], [[1.041, -0.449, 66.736, 0.709, 0.609, 0.05, 1], [0.831798, -4.184036, 1.0]], [[1.633, -0.225, 42.999, 2.61, 2.771, 0.5, 1], [0.422579, -2.420843, 1.0]], [[0.132, -0.838, 0.072, 0.412, 0.208, 0.01, 1], [0.12366, -0.834006, 1.0]]], [[[0.194, -0.92, 25.988, 2.712, 0.886, 0.1, 8], [-0.209628, 0.163927, 1.0]], [[0.058, 1.224, 42.535, 1.517, 0.328, 0.5, 5], [-0.000101, 0.006743, 1.0]], [[0.666, 1.74, 57.877, 2.068, 2.127, 0.1, 1], [0.657037, -0.089628, 1.0]], [[-0.368, -0.287, 23.934, 2.087, 1.05, 0.05, 2], [-0.364916, 0.133775, 1.0]], [[0.326, 0.466, 61.522, 2.623, 2.291, 0.01, 3], [0.335138, 0.223966, 1.0]], [[-0.519, -0.968, 62.61, 2.039, 0.152, 0.1, 6], [0.217976, 0.56902, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[1.416, 1.173, 43.299, 0.754, 2.467, 0.1, 1], [1.050042, -3.659575, 1.0]]], [[[-1.401, 0.585, 79.1, 0.893, 2.35, 0.5, 10], [-0.0, -0.0, 1.0]], [[-1.724, -0.957, 58.152, 0.412, 2.15, 0.01, 9], [-0.983354, 13.094621, 1.0]], [[1.914, 1.215, 50.88, 2.233, 2.185, 0.05, 7], [0.287159, -6.563774, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-1.199, 0.933, 73.961, 1.405, 2.45, 0.01, 5], [-1.067469, 3.680079, 1.0]], [[1.064, 1.69, 52.451, 0.532, 2.034, 0.5, 1], [0.143071, -1.841857, 1.0]], [[1.383, 1.181, 88.719, 2.586, 1.037, 0.01, 9], [1.278154, -2.961826, 1.0]], [[-0.458, -1.475, 76.199, 2.36, 0.385, 0.1, 1], [-0.457718, 0.002821, 1.0]]], [[[0.777, 1.994, 57.363, 2.434, 0.585, 0.01, 3], [0.82519, 1.409422, 1.0]], [[1.857, -0.996, 11.568, 1.719, 0.343, 0.5, 9], [-0.005974, -0.045732, 1.0]], [[-0.661, 0.376, 66.073, 2.358, 0.785, 0.01, 2], [-0.648053, 0.736934, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.421, 0.887, 67.079, 0.988, 1.223, 0.05, 9], [-0.17349, 0.037384, 1.0]], [[-0.828, -0.185, 67.12, 0.517, 2.786, 0.5, 10], [-0.0, -0.0, 1.0]], [[-1.587, 0.039, 21.152, 0.464, 2.072, 0.01, 12], [-1.152948, 5.796668, 1.0]], [[0.939, -0.391, 80.372, 0.897, 0.322, 0.5, 12], [-0.0, 0.0, 1.0]]], [[[1.48, -1.5, 47.647, 0.462, 0.24, 0.5, 7], [-1.4e-05, 2e-05, 1.0]], [[-1.472, -1.496, 89.833, 2.285, 2.932, 0.5, 13], [0.0, 1e-06, 1.0]], [[0.318, -1.09, 68.068, 1.546, 0.961, 0.5, 5], [0.000624, -0.001624, 1.0]], [[0.537, 1.491, 17.106, 2.255, 2.958, 0.05, 7], [0.657535, -0.41846, 1.0]], [[0.544, 1.609, 65.688, 1.374, 0.719, 0.01, 10], [0.552951, -1.128422, 1.0]], [[1.452, 1.003, 62.635, 0.47, 0.091, 0.01, 7], [1.023334, -10.790614, 1.0]], [[0.539, 1.387, 93.276, 0.984, 1.857, 0.5, 7], [-4e-06, -4.4e-05, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 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[0.0, 0.0, 0.0][0.0, 0.0, 0.0]Passed
case 1[0.136977, -0.818709, 1.0][0.067482, -1.242186, 1.0]Failed
case 2[0.094415, -0.246881, 1.0][0.064229, -0.321773, 1.0]Failed
case 3[-1.6217, 5.920045, 1.0][-1.618428, 6.062195, 1.0]Failed
case 4[1.057127, -1.971685, 1.0][1.056379, -2.022152, 1.0]Failed
case 5[0.828243, -4.255136, 1.0][0.831798, -4.184036, 1.0]Failed
case 6[0.850123, -1.565754, 1.0][0.422579, -2.420843, 1.0]Failed
case 7[0.123625, -0.837498, 1.0][0.12366, -0.834006, 1.0]Failed

SHA-256 / e4a610d1e569243c94ce1e8dd6b450f0addd1fa990d90e5acb30412217083753

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x0, v0, k, m, c, dt, steps):
    x, v = x0, v0
    def energy(p, q):
        return 0.5 * m * q * q + 0.5 * k * p * p
    e0 = energy(x, v)
    peak = 1.0 if e0 > 0 else 0.0
    for _ in range(steps):
        v = (v - dt * k * x / m) / (1 + dt * c / m + dt * dt * k / m)
        x = x + dt * v
        if e0 > 0:
            peak = max(peak, energy(x, v) / e0)
    return [round(x, 6), round(v, 6), round(peak, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.298, 1.963, 89.387, 2.381, 2.482, 0.1, 4], [0.067482, -1.242186, 1.0]], [[0.191, 1.945, 34.85, 2.123, 1.81, 0.5, 2], [0.064229, -0.321773, 1.0]], [[-1.795, -0.563, 97.971, 1.205, 2.366, 0.01, 5], [-1.618428, 6.062195, 1.0]], [[1.095, -1.653, 34.425, 1.885, 0.995, 0.01, 2], [1.056379, -2.022152, 1.0]], [[1.041, -0.449, 66.736, 0.709, 0.609, 0.05, 1], [0.831798, -4.184036, 1.0]], [[1.633, -0.225, 42.999, 2.61, 2.771, 0.5, 1], [0.422579, -2.420843, 1.0]], [[0.132, -0.838, 0.072, 0.412, 0.208, 0.01, 1], [0.12366, -0.834006, 1.0]]], [[[0.194, -0.92, 25.988, 2.712, 0.886, 0.1, 8], [-0.209628, 0.163927, 1.0]], [[0.058, 1.224, 42.535, 1.517, 0.328, 0.5, 5], [-0.000101, 0.006743, 1.0]], [[0.666, 1.74, 57.877, 2.068, 2.127, 0.1, 1], [0.657037, -0.089628, 1.0]], [[-0.368, -0.287, 23.934, 2.087, 1.05, 0.05, 2], [-0.364916, 0.133775, 1.0]], [[0.326, 0.466, 61.522, 2.623, 2.291, 0.01, 3], [0.335138, 0.223966, 1.0]], [[-0.519, -0.968, 62.61, 2.039, 0.152, 0.1, 6], [0.217976, 0.56902, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[1.416, 1.173, 43.299, 0.754, 2.467, 0.1, 1], [1.050042, -3.659575, 1.0]]], [[[-1.401, 0.585, 79.1, 0.893, 2.35, 0.5, 10], [-0.0, -0.0, 1.0]], [[-1.724, -0.957, 58.152, 0.412, 2.15, 0.01, 9], [-0.983354, 13.094621, 1.0]], [[1.914, 1.215, 50.88, 2.233, 2.185, 0.05, 7], [0.287159, -6.563774, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-1.199, 0.933, 73.961, 1.405, 2.45, 0.01, 5], [-1.067469, 3.680079, 1.0]], [[1.064, 1.69, 52.451, 0.532, 2.034, 0.5, 1], [0.143071, -1.841857, 1.0]], [[1.383, 1.181, 88.719, 2.586, 1.037, 0.01, 9], [1.278154, -2.961826, 1.0]], [[-0.458, -1.475, 76.199, 2.36, 0.385, 0.1, 1], [-0.457718, 0.002821, 1.0]]], [[[0.777, 1.994, 57.363, 2.434, 0.585, 0.01, 3], [0.82519, 1.409422, 1.0]], [[1.857, -0.996, 11.568, 1.719, 0.343, 0.5, 9], [-0.005974, -0.045732, 1.0]], [[-0.661, 0.376, 66.073, 2.358, 0.785, 0.01, 2], [-0.648053, 0.736934, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.421, 0.887, 67.079, 0.988, 1.223, 0.05, 9], [-0.17349, 0.037384, 1.0]], [[-0.828, -0.185, 67.12, 0.517, 2.786, 0.5, 10], [-0.0, -0.0, 1.0]], [[-1.587, 0.039, 21.152, 0.464, 2.072, 0.01, 12], [-1.152948, 5.796668, 1.0]], [[0.939, -0.391, 80.372, 0.897, 0.322, 0.5, 12], [-0.0, 0.0, 1.0]]], [[[1.48, -1.5, 47.647, 0.462, 0.24, 0.5, 7], [-1.4e-05, 2e-05, 1.0]], [[-1.472, -1.496, 89.833, 2.285, 2.932, 0.5, 13], [0.0, 1e-06, 1.0]], [[0.318, -1.09, 68.068, 1.546, 0.961, 0.5, 5], [0.000624, -0.001624, 1.0]], [[0.537, 1.491, 17.106, 2.255, 2.958, 0.05, 7], [0.657535, -0.41846, 1.0]], [[0.544, 1.609, 65.688, 1.374, 0.719, 0.01, 10], [0.552951, -1.128422, 1.0]], [[1.452, 1.003, 62.635, 0.47, 0.091, 0.01, 7], [1.023334, -10.790614, 1.0]], [[0.539, 1.387, 93.276, 0.984, 1.857, 0.5, 7], [-4e-06, -4.4e-05, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 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[0.0, 0.0, 0.0][0.0, 0.0, 0.0]Passed
case 1[0.067482, -1.242186, 1.0][0.067482, -1.242186, 1.0]Passed
case 2[0.064229, -0.321773, 1.0][0.064229, -0.321773, 1.0]Passed
case 3[-1.618428, 6.062195, 1.0][-1.618428, 6.062195, 1.0]Passed
case 4[1.056379, -2.022152, 1.0][1.056379, -2.022152, 1.0]Passed
case 5[0.831798, -4.184036, 1.0][0.831798, -4.184036, 1.0]Passed
case 6[0.422579, -2.420843, 1.0][0.422579, -2.420843, 1.0]Passed
case 7[0.12366, -0.834006, 1.0][0.12366, -0.834006, 1.0]Passed

SHA-256 / 4e9f23d17720af4d6cf4241b0160a467cb529a9cf0bdcaa2014a850383db940d

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

Case digest / 541f6823056647b283dda625456c964fec176a5c7d991b5d927e3d09cdd8a085