FAILURE MAP
← Case archive

FA-86841 / Physics integrator stability / Open access

Symplectic Euler substep drifts position with the pre-kick velocity · case 01

A stiff spring that should orbit with bounded energy gains amplitude every frame.

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

ROOT CAUSE

The position update runs before the velocity kick, turning the scheme into explicit forward Euler.

VERIFIED REPAIR

Kick the velocity first and drift the position with the freshly updated velocity.

Unsuccessful approach: Averaging old and new velocity for the drift gives a trapezoid-like scheme that is not the contracted symplectic update.

Case contract

solve(x0, v0, k, m, c, dt, steps): damped spring a=(-k*x-c*v)/m integrated with semi-implicit Euler (velocity first, then position with the new velocity). Each frame of length dt is split into ceil(dt/0.05) equal substeps (at least one). Return [x, v] rounded to 6 decimals after steps frames.

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):
    n_sub = max(1, math.ceil(dt / 0.05 - 1e-12))
    h = dt / n_sub
    x, v = x0, v0
    for _ in range(steps):
        for _ in range(n_sub):
            a = (-k * x - c * v) / m
            x = x + v * h
            v = v + a * h
    return [round(x, 6), round(v, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.958, 1.121, 24.102, 1.592, 0.332, 0.04, 3], [-0.694302, 2.683815]], [[-1.072, -0.219, 22.982, 2.32, 1.666, 0.05, 1], [-1.056009, 0.319827]], [[1.454, -0.539, 11.28, 2.71, 0.785, 0.07, 0], [1.454, -0.539]], [[1.545, 1.962, 22.255, 1.613, 1.385, 0.02, 0], [1.545, 1.962]], [[0.428, -0.176, 18.564, 2.203, 1.454, 0.07, 2], [0.36285, -0.625106]], [[0.514, 1.143, 25.432, 0.518, 1.496, 0.13, 9], [-0.010702, -0.661591]], [[-1.539, -1.291, 32.554, 2.072, 1.066, 0.07, 3], [-1.199771, 3.497044]]], [[[1.742, -0.907, 38.421, 2.244, 0.819, 0.1, 5], [-1.031195, -5.442883]], [[-1.037, 0.234, 1.989, 0.946, 0.544, 0.01, 8], [-1.011063, 0.39269]], [[1.602, 1.18, 24.517, 1.738, 1.228, 0.05, 12], [-0.636953, -4.398087]], [[-0.384, 0.483, 39.776, 2.351, 1.463, 0.04, 0], [-0.384, 0.483]], [[-1.526, 0.873, 6.362, 1.828, 1.137, 0.13, 0], [-1.526, 0.873]], [[-1.985, 0.451, 27.993, 1.055, 0.395, 0.02, 2], [-1.904435, 2.527224]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.365, 1.406, 18.892, 1.116, 1.872, 0.05, 6], [0.118578, 1.39259]]], [[[1.454, 1.519, 3.577, 1.401, 0.303, 0.04, 8], [1.697018, 0.15014]], [[-1.712, -1.9, 9.139, 0.693, 0.61, 0.05, 13], [0.624756, 4.351887]], [[0.87, 1.182, 19.781, 1.146, 0.3, 0.1, 0], [0.87, 1.182]], [[0.116, -1.998, 11.275, 2.342, 1.481, 0.1, 12], [-0.366142, 1.157058]], [[-0.541, 0.707, 32.675, 1.469, 1.372, 0.2, 9], [0.199962, 0.643469]], [[-1.675, 0.095, 12.217, 1.214, 0.078, 0.13, 11], [0.14895, -5.038149]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[1.648, -0.117, 24.609, 0.651, 1.732, 0.05, 0], [1.648, -0.117]]], [[[0.286, 1.636, 25.073, 1.437, 1.309, 0.1, 0], [0.286, 1.636]], [[-0.07, 1.272, 25.671, 1.8, 0.333, 0.02, 8], [0.132138, 1.188849]], [[-1.284, 1.069, 32.194, 0.542, 0.353, 0.05, 11], [0.174523, -8.251404]], [[-1.975, -1.176, 27.308, 0.777, 1.386, 0.04, 3], [-1.482343, 6.386036]], [[1.974, -1.489, 34.073, 1.218, 1.372, 0.05, 10], [-1.429284, -2.634338]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.484, 0.239, 12.355, 0.868, 0.3, 0.07, 0], [0.484, 0.239]], [[-0.117, 1.853, 21.165, 2.719, 0.626, 0.07, 12], [0.503218, -0.957084]]], [[[-0.078, -0.863, 3.994, 2.622, 1.462, 0.1, 0], [-0.078, -0.863]], [[-0.056, -1.079, 26.249, 1.108, 0.441, 0.07, 8], [-0.030782, 0.966174]], [[0.855, 1.882, 14.464, 2.712, 0.17, 0.02, 0], [0.855, 1.882]], [[0.633, -1.784, 4.173, 2.063, 1.163, 0.13, 21], [0.109799, 0.812645]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.29, -0.513, 1.621, 0.73, 0.535, 0.2, 8], [-0.052879, 0.418713]], [[1.617, 1.871, 21.063, 0.682, 0.338, 0.02, 7], [1.325932, -4.832529]], [[1.925, -1.211, 19.503, 2.115, 0.02, 0.13, 1], [1.574252, -3.400654]]]]
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[1.0, 0.0][1.0, 0.0]Passed
case 1[-0.756262, 2.724071][-0.694302, 2.683815]Failed
case 2[-1.08295, 0.319827][-1.056009, 0.319827]Failed
case 3[1.454, -0.539][1.454, -0.539]Passed
case 4[1.545, 1.962][1.545, 1.962]Passed
case 5[0.378395, -0.63229][0.36285, -0.625106]Failed
case 6[0.021512, -2.569141][-0.010702, -0.661591]Failed
case 7[-1.356003, 3.729909][-1.199771, 3.497044]Failed

SHA-256 / e4825c66a36c03ef84fc6bf3cffece47887de3ae7899eb463dd5a92119c959cc

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):
    n_sub = max(1, math.ceil(dt / 0.05 - 1e-12))
    h = dt / n_sub
    x, v = x0, v0
    for _ in range(steps):
        for _ in range(n_sub):
            a = (-k * x - c * v) / m
            vn = v + a * h
            x = x + 0.5 * (v + vn) * h
            v = vn
    return [round(x, 6), round(v, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.958, 1.121, 24.102, 1.592, 0.332, 0.04, 3], [-0.694302, 2.683815]], [[-1.072, -0.219, 22.982, 2.32, 1.666, 0.05, 1], [-1.056009, 0.319827]], [[1.454, -0.539, 11.28, 2.71, 0.785, 0.07, 0], [1.454, -0.539]], [[1.545, 1.962, 22.255, 1.613, 1.385, 0.02, 0], [1.545, 1.962]], [[0.428, -0.176, 18.564, 2.203, 1.454, 0.07, 2], [0.36285, -0.625106]], [[0.514, 1.143, 25.432, 0.518, 1.496, 0.13, 9], [-0.010702, -0.661591]], [[-1.539, -1.291, 32.554, 2.072, 1.066, 0.07, 3], [-1.199771, 3.497044]]], [[[1.742, -0.907, 38.421, 2.244, 0.819, 0.1, 5], [-1.031195, -5.442883]], [[-1.037, 0.234, 1.989, 0.946, 0.544, 0.01, 8], [-1.011063, 0.39269]], [[1.602, 1.18, 24.517, 1.738, 1.228, 0.05, 12], [-0.636953, -4.398087]], [[-0.384, 0.483, 39.776, 2.351, 1.463, 0.04, 0], [-0.384, 0.483]], [[-1.526, 0.873, 6.362, 1.828, 1.137, 0.13, 0], [-1.526, 0.873]], [[-1.985, 0.451, 27.993, 1.055, 0.395, 0.02, 2], [-1.904435, 2.527224]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.365, 1.406, 18.892, 1.116, 1.872, 0.05, 6], [0.118578, 1.39259]]], [[[1.454, 1.519, 3.577, 1.401, 0.303, 0.04, 8], [1.697018, 0.15014]], [[-1.712, -1.9, 9.139, 0.693, 0.61, 0.05, 13], [0.624756, 4.351887]], [[0.87, 1.182, 19.781, 1.146, 0.3, 0.1, 0], [0.87, 1.182]], [[0.116, -1.998, 11.275, 2.342, 1.481, 0.1, 12], [-0.366142, 1.157058]], [[-0.541, 0.707, 32.675, 1.469, 1.372, 0.2, 9], [0.199962, 0.643469]], [[-1.675, 0.095, 12.217, 1.214, 0.078, 0.13, 11], [0.14895, -5.038149]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[1.648, -0.117, 24.609, 0.651, 1.732, 0.05, 0], [1.648, -0.117]]], [[[0.286, 1.636, 25.073, 1.437, 1.309, 0.1, 0], [0.286, 1.636]], [[-0.07, 1.272, 25.671, 1.8, 0.333, 0.02, 8], [0.132138, 1.188849]], [[-1.284, 1.069, 32.194, 0.542, 0.353, 0.05, 11], [0.174523, -8.251404]], [[-1.975, -1.176, 27.308, 0.777, 1.386, 0.04, 3], [-1.482343, 6.386036]], [[1.974, -1.489, 34.073, 1.218, 1.372, 0.05, 10], [-1.429284, -2.634338]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.484, 0.239, 12.355, 0.868, 0.3, 0.07, 0], [0.484, 0.239]], [[-0.117, 1.853, 21.165, 2.719, 0.626, 0.07, 12], [0.503218, -0.957084]]], [[[-0.078, -0.863, 3.994, 2.622, 1.462, 0.1, 0], [-0.078, -0.863]], [[-0.056, -1.079, 26.249, 1.108, 0.441, 0.07, 8], [-0.030782, 0.966174]], [[0.855, 1.882, 14.464, 2.712, 0.17, 0.02, 0], [0.855, 1.882]], [[0.633, -1.784, 4.173, 2.063, 1.163, 0.13, 21], [0.109799, 0.812645]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.29, -0.513, 1.621, 0.73, 0.535, 0.2, 8], [-0.052879, 0.418713]], [[1.617, 1.871, 21.063, 0.682, 0.338, 0.02, 7], [1.325932, -4.832529]], [[1.925, -1.211, 19.503, 2.115, 0.02, 0.13, 1], [1.574252, -3.400654]]]]
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[1.0, 0.0][1.0, 0.0]Passed
case 1[-0.724881, 2.703859][-0.694302, 2.683815]Failed
case 2[-1.069479, 0.319827][-1.056009, 0.319827]Failed
case 3[1.454, -0.539][1.454, -0.539]Passed
case 4[1.545, 1.962][1.545, 1.962]Passed
case 5[0.37056, -0.628685][0.36285, -0.625106]Failed
case 6[-0.010003, -1.316001][-0.010702, -0.661591]Failed
case 7[-1.275917, 3.611958][-1.199771, 3.497044]Failed

SHA-256 / 2d4a1157e497fa18200bb64c91ad6f71d123d21e5190d0710a15eea3dba22edd

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):
    n_sub = max(1, math.ceil(dt / 0.05 - 1e-12))
    h = dt / n_sub
    x, v = x0, v0
    for _ in range(steps):
        for _ in range(n_sub):
            a = (-k * x - c * v) / m
            v = v + a * h
            x = x + v * h
    return [round(x, 6), round(v, 6)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.958, 1.121, 24.102, 1.592, 0.332, 0.04, 3], [-0.694302, 2.683815]], [[-1.072, -0.219, 22.982, 2.32, 1.666, 0.05, 1], [-1.056009, 0.319827]], [[1.454, -0.539, 11.28, 2.71, 0.785, 0.07, 0], [1.454, -0.539]], [[1.545, 1.962, 22.255, 1.613, 1.385, 0.02, 0], [1.545, 1.962]], [[0.428, -0.176, 18.564, 2.203, 1.454, 0.07, 2], [0.36285, -0.625106]], [[0.514, 1.143, 25.432, 0.518, 1.496, 0.13, 9], [-0.010702, -0.661591]], [[-1.539, -1.291, 32.554, 2.072, 1.066, 0.07, 3], [-1.199771, 3.497044]]], [[[1.742, -0.907, 38.421, 2.244, 0.819, 0.1, 5], [-1.031195, -5.442883]], [[-1.037, 0.234, 1.989, 0.946, 0.544, 0.01, 8], [-1.011063, 0.39269]], [[1.602, 1.18, 24.517, 1.738, 1.228, 0.05, 12], [-0.636953, -4.398087]], [[-0.384, 0.483, 39.776, 2.351, 1.463, 0.04, 0], [-0.384, 0.483]], [[-1.526, 0.873, 6.362, 1.828, 1.137, 0.13, 0], [-1.526, 0.873]], [[-1.985, 0.451, 27.993, 1.055, 0.395, 0.02, 2], [-1.904435, 2.527224]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.365, 1.406, 18.892, 1.116, 1.872, 0.05, 6], [0.118578, 1.39259]]], [[[1.454, 1.519, 3.577, 1.401, 0.303, 0.04, 8], [1.697018, 0.15014]], [[-1.712, -1.9, 9.139, 0.693, 0.61, 0.05, 13], [0.624756, 4.351887]], [[0.87, 1.182, 19.781, 1.146, 0.3, 0.1, 0], [0.87, 1.182]], [[0.116, -1.998, 11.275, 2.342, 1.481, 0.1, 12], [-0.366142, 1.157058]], [[-0.541, 0.707, 32.675, 1.469, 1.372, 0.2, 9], [0.199962, 0.643469]], [[-1.675, 0.095, 12.217, 1.214, 0.078, 0.13, 11], [0.14895, -5.038149]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[1.648, -0.117, 24.609, 0.651, 1.732, 0.05, 0], [1.648, -0.117]]], [[[0.286, 1.636, 25.073, 1.437, 1.309, 0.1, 0], [0.286, 1.636]], [[-0.07, 1.272, 25.671, 1.8, 0.333, 0.02, 8], [0.132138, 1.188849]], [[-1.284, 1.069, 32.194, 0.542, 0.353, 0.05, 11], [0.174523, -8.251404]], [[-1.975, -1.176, 27.308, 0.777, 1.386, 0.04, 3], [-1.482343, 6.386036]], [[1.974, -1.489, 34.073, 1.218, 1.372, 0.05, 10], [-1.429284, -2.634338]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.484, 0.239, 12.355, 0.868, 0.3, 0.07, 0], [0.484, 0.239]], [[-0.117, 1.853, 21.165, 2.719, 0.626, 0.07, 12], [0.503218, -0.957084]]], [[[-0.078, -0.863, 3.994, 2.622, 1.462, 0.1, 0], [-0.078, -0.863]], [[-0.056, -1.079, 26.249, 1.108, 0.441, 0.07, 8], [-0.030782, 0.966174]], [[0.855, 1.882, 14.464, 2.712, 0.17, 0.02, 0], [0.855, 1.882]], [[0.633, -1.784, 4.173, 2.063, 1.163, 0.13, 21], [0.109799, 0.812645]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.29, -0.513, 1.621, 0.73, 0.535, 0.2, 8], [-0.052879, 0.418713]], [[1.617, 1.871, 21.063, 0.682, 0.338, 0.02, 7], [1.325932, -4.832529]], [[1.925, -1.211, 19.503, 2.115, 0.02, 0.13, 1], [1.574252, -3.400654]]]]
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[1.0, 0.0][1.0, 0.0]Passed
case 1[-0.694302, 2.683815][-0.694302, 2.683815]Passed
case 2[-1.056009, 0.319827][-1.056009, 0.319827]Passed
case 3[1.454, -0.539][1.454, -0.539]Passed
case 4[1.545, 1.962][1.545, 1.962]Passed
case 5[0.36285, -0.625106][0.36285, -0.625106]Passed
case 6[-0.010702, -0.661591][-0.010702, -0.661591]Passed
case 7[-1.199771, 3.497044][-1.199771, 3.497044]Passed

SHA-256 / 286e22f59c246923bd6ea63f46236f3851edb388dbc20c0f7ebbe79cc7f9346c

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

Case digest / 8c553462074da18ed925a8dc16055713a325a9dfe76281f2a86052ff8ec3a6c2