FA-87106 / Physics integrator stability / Open access
Energy monitor doubles the spring potential · case 01
Peak energy ratio is inflated whenever the spring is stretched.
ROOT CAUSE
Potential energy is k*x^2.
VERIFIED REPAIR
Use 0.5*k*x^2.
Unsuccessful approach: Using mass instead of stiffness in the potential mixes units.
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 + 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.24, -0.911, 49.012, 1.578, 2.704, 0.1, 5], [-0.09708, -0.084706, 1.0]], [[1.287, -0.056, 59.572, 2.284, 0.95, 0.01, 3], [1.265475, -1.044567, 1.0]], [[-0.665, 1.365, 36.996, 1.759, 0.53, 0.5, 3], [0.035382, -0.128106, 1.0]], [[-0.386, -0.618, 22.711, 1.957, 1.669, 0.05, 3], [-0.405267, 0.106477, 1.0]], [[1.126, 0.46, 23.793, 1.74, 1.365, 0.01, 2], [1.130507, 0.147492, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-0.525, -1.536, 11.54, 2.318, 2.111, 0.1, 6], [-0.633115, 0.624515, 1.0]], [[0.798, 0.799, 72.701, 2.561, 2.07, 0.05, 3], [0.616397, -2.061708, 1.0]]], [[[0.326, 0.466, 61.522, 2.623, 2.291, 0.01, 3], [0.335138, 0.223966, 1.0]], [[-1.894, -0.891, 20.673, 0.599, 1.526, 0.5, 6], [-0.001474, -0.000425, 1.0]], [[-0.309, -1.711, 54.616, 0.839, 0.325, 0.5, 6], [-4.2e-05, 0.000468, 1.0]], [[-1.228, -0.451, 23.36, 2.048, 1.522, 0.01, 2], [-1.232747, -0.166441, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.614, -0.235, 4.708, 0.51, 0.987, 0.05, 3], [0.517177, -0.816611, 1.0]], [[-1.593, 1.89, 51.296, 0.937, 1.405, 0.01, 5], [-1.382702, 5.629739, 1.0]], [[-1.267, -0.184, 5.424, 1.543, 1.623, 0.01, 7], [-1.267247, 0.128498, 1.0]]], [[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[1.099, -1.351, 78.259, 0.426, 0.263, 0.05, 3], [-0.156961, -7.876689, 1.0]], [[-1.054, -0.343, 49.43, 2.124, 2.635, 0.05, 1], [-1.014568, 0.788639, 1.0]], [[0.257, 1.826, 18.613, 1.995, 1.806, 0.1, 7], [0.273046, -0.937505, 1.0]], [[-1.814, -0.942, 9.978, 1.746, 0.712, 0.01, 9], [-1.850539, 0.019999, 1.0]], [[-1.949, -0.338, 47.923, 2.883, 2.062, 0.1, 3], [-0.781938, 5.129576, 1.0]], [[-0.678, -1.597, 66.475, 1.112, 2.962, 0.01, 5], [-0.693206, 0.522371, 1.0]], [[0.787, -0.883, 93.251, 1.633, 2.06, 0.05, 3], [0.262131, -4.192805, 1.0]]], [[[-1.031, -0.971, 69.316, 1.698, 1.184, 0.01, 12], [-0.833114, 3.639745, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.283, 1.441, 38.896, 1.952, 0.391, 0.05, 8], [0.217372, -1.212269, 1.0]], [[-0.933, 1.78, 8.086, 0.726, 0.672, 0.1, 2], [-0.439694, 2.591455, 1.0]], [[-1.345, -0.611, 81.079, 1.16, 2.627, 0.01, 4], [-1.279534, 2.916997, 1.0]], [[0.48, 0.698, 50.79, 1.825, 2.065, 0.05, 5], [0.243232, -1.683845, 1.0]], [[-0.239, -1.564, 66.752, 1.696, 1.527, 0.01, 3], [-0.278987, -1.213386, 1.0]], [[1.505, -0.392, 75.224, 1.938, 0.736, 0.5, 10], [1e-05, 1.2e-05, 1.0]]], [[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-1.508, 1.83, 17.985, 1.33, 1.085, 0.01, 10], [-1.231328, 3.455601, 1.0]], [[-1.039, -1.09, 32.245, 0.581, 1.221, 0.1, 8], [0.030932, -0.820883, 1.0]], [[-1.064, 1.282, 76.595, 2.19, 0.837, 0.05, 8], [0.553758, 2.614546, 1.0]], [[1.155, 1.357, 6.477, 2.373, 1.143, 0.1, 4], [1.316819, -0.14298, 1.0]], [[0.031, 0.742, 81.09, 2.83, 2.129, 0.05, 4], [0.111129, 0.16107, 1.0]], [[0.544, 1.609, 65.688, 1.374, 0.719, 0.01, 10], [0.552951, -1.128422, 1.0]], [[1.48, -1.5, 47.647, 0.462, 0.24, 0.5, 7], [-1.4e-05, 2e-05, 1.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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| case 0 | [-0.09708, -0.084706, 1.0] | [-0.09708, -0.084706, 1.0] | Passed |
| case 1 | [1.265475, -1.044567, 1.0] | [1.265475, -1.044567, 1.0] | Passed |
| case 2 | [0.035382, -0.128106, 1.0] | [0.035382, -0.128106, 1.0] | Passed |
| case 3 | [-0.405267, 0.106477, 1.02357] | [-0.405267, 0.106477, 1.0] | Failed |
| case 4 | [1.130507, 0.147492, 1.002531] | [1.130507, 0.147492, 1.0] | Failed |
| case 5 | [0.0, 0.0, 0.0] | [0.0, 0.0, 0.0] | Passed |
| case 6 | [-0.633115, 0.624515, 1.079553] | [-0.633115, 0.624515, 1.0] | Failed |
| case 7 | [0.616397, -2.061708, 1.0] | [0.616397, -2.061708, 1.0] | Passed |
SHA-256 / fec480bab1f0f1e16b715c1e5d3d520aa403874b5bf3052ed6787a540e2d2ab4
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 * m * 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.24, -0.911, 49.012, 1.578, 2.704, 0.1, 5], [-0.09708, -0.084706, 1.0]], [[1.287, -0.056, 59.572, 2.284, 0.95, 0.01, 3], [1.265475, -1.044567, 1.0]], [[-0.665, 1.365, 36.996, 1.759, 0.53, 0.5, 3], [0.035382, -0.128106, 1.0]], [[-0.386, -0.618, 22.711, 1.957, 1.669, 0.05, 3], [-0.405267, 0.106477, 1.0]], [[1.126, 0.46, 23.793, 1.74, 1.365, 0.01, 2], [1.130507, 0.147492, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-0.525, -1.536, 11.54, 2.318, 2.111, 0.1, 6], [-0.633115, 0.624515, 1.0]], [[0.798, 0.799, 72.701, 2.561, 2.07, 0.05, 3], [0.616397, -2.061708, 1.0]]], [[[0.326, 0.466, 61.522, 2.623, 2.291, 0.01, 3], [0.335138, 0.223966, 1.0]], [[-1.894, -0.891, 20.673, 0.599, 1.526, 0.5, 6], [-0.001474, -0.000425, 1.0]], [[-0.309, -1.711, 54.616, 0.839, 0.325, 0.5, 6], [-4.2e-05, 0.000468, 1.0]], [[-1.228, -0.451, 23.36, 2.048, 1.522, 0.01, 2], [-1.232747, -0.166441, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.614, -0.235, 4.708, 0.51, 0.987, 0.05, 3], [0.517177, -0.816611, 1.0]], [[-1.593, 1.89, 51.296, 0.937, 1.405, 0.01, 5], [-1.382702, 5.629739, 1.0]], [[-1.267, -0.184, 5.424, 1.543, 1.623, 0.01, 7], [-1.267247, 0.128498, 1.0]]], [[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[1.099, -1.351, 78.259, 0.426, 0.263, 0.05, 3], [-0.156961, -7.876689, 1.0]], [[-1.054, -0.343, 49.43, 2.124, 2.635, 0.05, 1], [-1.014568, 0.788639, 1.0]], [[0.257, 1.826, 18.613, 1.995, 1.806, 0.1, 7], [0.273046, -0.937505, 1.0]], [[-1.814, -0.942, 9.978, 1.746, 0.712, 0.01, 9], [-1.850539, 0.019999, 1.0]], [[-1.949, -0.338, 47.923, 2.883, 2.062, 0.1, 3], [-0.781938, 5.129576, 1.0]], [[-0.678, -1.597, 66.475, 1.112, 2.962, 0.01, 5], [-0.693206, 0.522371, 1.0]], [[0.787, -0.883, 93.251, 1.633, 2.06, 0.05, 3], [0.262131, -4.192805, 1.0]]], [[[-1.031, -0.971, 69.316, 1.698, 1.184, 0.01, 12], [-0.833114, 3.639745, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.283, 1.441, 38.896, 1.952, 0.391, 0.05, 8], [0.217372, -1.212269, 1.0]], [[-0.933, 1.78, 8.086, 0.726, 0.672, 0.1, 2], [-0.439694, 2.591455, 1.0]], [[-1.345, -0.611, 81.079, 1.16, 2.627, 0.01, 4], [-1.279534, 2.916997, 1.0]], [[0.48, 0.698, 50.79, 1.825, 2.065, 0.05, 5], [0.243232, -1.683845, 1.0]], [[-0.239, -1.564, 66.752, 1.696, 1.527, 0.01, 3], [-0.278987, -1.213386, 1.0]], [[1.505, -0.392, 75.224, 1.938, 0.736, 0.5, 10], [1e-05, 1.2e-05, 1.0]]], [[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-1.508, 1.83, 17.985, 1.33, 1.085, 0.01, 10], [-1.231328, 3.455601, 1.0]], [[-1.039, -1.09, 32.245, 0.581, 1.221, 0.1, 8], [0.030932, -0.820883, 1.0]], [[-1.064, 1.282, 76.595, 2.19, 0.837, 0.05, 8], [0.553758, 2.614546, 1.0]], [[1.155, 1.357, 6.477, 2.373, 1.143, 0.1, 4], [1.316819, -0.14298, 1.0]], [[0.031, 0.742, 81.09, 2.83, 2.129, 0.05, 4], [0.111129, 0.16107, 1.0]], [[0.544, 1.609, 65.688, 1.374, 0.719, 0.01, 10], [0.552951, -1.128422, 1.0]], [[1.48, -1.5, 47.647, 0.462, 0.24, 0.5, 7], [-1.4e-05, 2e-05, 1.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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| case 0 | [-0.09708, -0.084706, 1.42619] | [-0.09708, -0.084706, 1.0] | Failed |
| case 1 | [1.265475, -1.044567, 1.6225] | [1.265475, -1.044567, 1.0] | Failed |
| case 2 | [0.035382, -0.128106, 1.0] | [0.035382, -0.128106, 1.0] | Passed |
| case 3 | [-0.405267, 0.106477, 1.0] | [-0.405267, 0.106477, 1.0] | Passed |
| case 4 | [1.130507, 0.147492, 1.0] | [1.130507, 0.147492, 1.0] | Passed |
| case 5 | [0.0, 0.0, 0.0] | [0.0, 0.0, 0.0] | Passed |
| case 6 | [-0.633115, 0.624515, 1.0] | [-0.633115, 0.624515, 1.0] | Passed |
| case 7 | [0.616397, -2.061708, 3.631246] | [0.616397, -2.061708, 1.0] | Failed |
SHA-256 / b68e8ad5c400cc76c4a5ce90cc6f173ca7c63d650fbf9553424ce724a8f6bc7e
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.24, -0.911, 49.012, 1.578, 2.704, 0.1, 5], [-0.09708, -0.084706, 1.0]], [[1.287, -0.056, 59.572, 2.284, 0.95, 0.01, 3], [1.265475, -1.044567, 1.0]], [[-0.665, 1.365, 36.996, 1.759, 0.53, 0.5, 3], [0.035382, -0.128106, 1.0]], [[-0.386, -0.618, 22.711, 1.957, 1.669, 0.05, 3], [-0.405267, 0.106477, 1.0]], [[1.126, 0.46, 23.793, 1.74, 1.365, 0.01, 2], [1.130507, 0.147492, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-0.525, -1.536, 11.54, 2.318, 2.111, 0.1, 6], [-0.633115, 0.624515, 1.0]], [[0.798, 0.799, 72.701, 2.561, 2.07, 0.05, 3], [0.616397, -2.061708, 1.0]]], [[[0.326, 0.466, 61.522, 2.623, 2.291, 0.01, 3], [0.335138, 0.223966, 1.0]], [[-1.894, -0.891, 20.673, 0.599, 1.526, 0.5, 6], [-0.001474, -0.000425, 1.0]], [[-0.309, -1.711, 54.616, 0.839, 0.325, 0.5, 6], [-4.2e-05, 0.000468, 1.0]], [[-1.228, -0.451, 23.36, 2.048, 1.522, 0.01, 2], [-1.232747, -0.166441, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.614, -0.235, 4.708, 0.51, 0.987, 0.05, 3], [0.517177, -0.816611, 1.0]], [[-1.593, 1.89, 51.296, 0.937, 1.405, 0.01, 5], [-1.382702, 5.629739, 1.0]], [[-1.267, -0.184, 5.424, 1.543, 1.623, 0.01, 7], [-1.267247, 0.128498, 1.0]]], [[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[1.099, -1.351, 78.259, 0.426, 0.263, 0.05, 3], [-0.156961, -7.876689, 1.0]], [[-1.054, -0.343, 49.43, 2.124, 2.635, 0.05, 1], [-1.014568, 0.788639, 1.0]], [[0.257, 1.826, 18.613, 1.995, 1.806, 0.1, 7], [0.273046, -0.937505, 1.0]], [[-1.814, -0.942, 9.978, 1.746, 0.712, 0.01, 9], [-1.850539, 0.019999, 1.0]], [[-1.949, -0.338, 47.923, 2.883, 2.062, 0.1, 3], [-0.781938, 5.129576, 1.0]], [[-0.678, -1.597, 66.475, 1.112, 2.962, 0.01, 5], [-0.693206, 0.522371, 1.0]], [[0.787, -0.883, 93.251, 1.633, 2.06, 0.05, 3], [0.262131, -4.192805, 1.0]]], [[[-1.031, -0.971, 69.316, 1.698, 1.184, 0.01, 12], [-0.833114, 3.639745, 1.0]], [[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[0.283, 1.441, 38.896, 1.952, 0.391, 0.05, 8], [0.217372, -1.212269, 1.0]], [[-0.933, 1.78, 8.086, 0.726, 0.672, 0.1, 2], [-0.439694, 2.591455, 1.0]], [[-1.345, -0.611, 81.079, 1.16, 2.627, 0.01, 4], [-1.279534, 2.916997, 1.0]], [[0.48, 0.698, 50.79, 1.825, 2.065, 0.05, 5], [0.243232, -1.683845, 1.0]], [[-0.239, -1.564, 66.752, 1.696, 1.527, 0.01, 3], [-0.278987, -1.213386, 1.0]], [[1.505, -0.392, 75.224, 1.938, 0.736, 0.5, 10], [1e-05, 1.2e-05, 1.0]]], [[[0.0, 0.0, 5.0, 1.0, 0.5, 0.1, 3], [0.0, 0.0, 0.0]], [[-1.508, 1.83, 17.985, 1.33, 1.085, 0.01, 10], [-1.231328, 3.455601, 1.0]], [[-1.039, -1.09, 32.245, 0.581, 1.221, 0.1, 8], [0.030932, -0.820883, 1.0]], [[-1.064, 1.282, 76.595, 2.19, 0.837, 0.05, 8], [0.553758, 2.614546, 1.0]], [[1.155, 1.357, 6.477, 2.373, 1.143, 0.1, 4], [1.316819, -0.14298, 1.0]], [[0.031, 0.742, 81.09, 2.83, 2.129, 0.05, 4], [0.111129, 0.16107, 1.0]], [[0.544, 1.609, 65.688, 1.374, 0.719, 0.01, 10], [0.552951, -1.128422, 1.0]], [[1.48, -1.5, 47.647, 0.462, 0.24, 0.5, 7], [-1.4e-05, 2e-05, 1.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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| case 0 | [-0.09708, -0.084706, 1.0] | [-0.09708, -0.084706, 1.0] | Passed |
| case 1 | [1.265475, -1.044567, 1.0] | [1.265475, -1.044567, 1.0] | Passed |
| case 2 | [0.035382, -0.128106, 1.0] | [0.035382, -0.128106, 1.0] | Passed |
| case 3 | [-0.405267, 0.106477, 1.0] | [-0.405267, 0.106477, 1.0] | Passed |
| case 4 | [1.130507, 0.147492, 1.0] | [1.130507, 0.147492, 1.0] | Passed |
| case 5 | [0.0, 0.0, 0.0] | [0.0, 0.0, 0.0] | Passed |
| case 6 | [-0.633115, 0.624515, 1.0] | [-0.633115, 0.624515, 1.0] | Passed |
| case 7 | [0.616397, -2.061708, 1.0] | [0.616397, -2.061708, 1.0] | Passed |
SHA-256 / e73b1710a4c237c0759349e06b9bfcb656efb966e10d1a704cf40fd4e40522ae
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.747954+00:00.
Case digest / 97e61e5fc7105fea514e2736e2c5733bd93bb0abdb2ed3e0e4e97e287bfccde6