FAILURE MAP
← Case archive

FA-86856 / Physics integrator stability / Open access

Substep count truncates the frame to cell size ratio · case 01

Frames slightly longer than the 0.05 limit run one oversized substep and lose stability margin.

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

ROOT CAUSE

The substep count uses int(dt/0.05), flooring 0.07 to one substep of 0.07.

VERIFIED REPAIR

Round the ratio up with ceil so every substep is at most 0.05.

Unsuccessful approach: round() still produces too few substeps for ratios with fractional part below one half.

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, int(dt / 0.05))
    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.539, -1.291, 32.554, 2.072, 1.066, 0.07, 3], [-1.199771, 3.497044]], [[-1.603, 0.842, 17.725, 1.501, 1.753, 0.13, 8], [0.763682, -1.657161]], [[-0.104, -1.626, 5.159, 2.304, 0.637, 0.2, 0], [-0.104, -1.626]], [[1.295, 1.487, 26.631, 0.848, 0.944, 0.07, 2], [1.013808, -3.889523]], [[0.428, -0.176, 18.564, 2.203, 1.454, 0.07, 2], [0.36285, -0.625106]], [[1.083, 0.787, 2.365, 2.971, 0.937, 0.13, 0], [1.083, 0.787]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]]], [[[-1.643, 1.58, 13.795, 1.143, 0.679, 0.13, 8], [0.915666, -2.988323]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.925, -0.173, 39.051, 1.209, 0.422, 0.01, 5], [1.824415, -3.211948]], [[1.023, -1.573, 6.771, 1.338, 0.17, 0.07, 1], [0.895005, -1.909789]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.394, -0.949, 26.39, 2.243, 1.907, 0.13, 4], [-0.264175, -0.874209]], [[-0.479, 1.886, 38.122, 2.825, 1.885, 0.07, 7], [0.501848, 1.056904]], [[0.193, -0.706, 30.766, 2.826, 1.227, 0.07, 7], [-0.197706, -0.539247]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[0.236, -1.957, 4.07, 0.843, 0.292, 0.07, 2], [-0.039504, -1.950963]], [[1.111, 1.678, 35.58, 2.166, 0.299, 0.13, 8], [-0.758409, 2.897396]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.784, -0.426, 6.875, 0.731, 0.044, 0.1, 14], [-0.132405, 2.308875]], [[1.207, 0.351, 14.617, 2.861, 0.895, 0.07, 6], [0.802339, -1.906391]], [[1.233, -0.932, 9.854, 1.176, 1.509, 0.07, 12], [-0.56207, -0.986805]], [[-0.948, -1.497, 32.937, 1.389, 1.23, 0.07, 1], [-0.965534, 0.162831]]], [[[1.142, -1.241, 14.348, 1.124, 0.842, 0.07, 7], [-0.412514, -3.148043]], [[1.948, 0.758, 13.319, 0.711, 1.638, 0.04, 18], [-0.799165, -0.570716]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.413, 0.664, 29.552, 2.975, 1.21, 0.13, 8], [1.104015, -1.060714]], [[-0.582, -1.279, 4.833, 1.259, 1.426, 0.07, 3], [-0.756223, -0.513119]], [[0.76, 1.611, 15.529, 0.686, 0.511, 0.07, 14], [-0.242783, 2.493606]], [[0.603, 1.49, 19.896, 2.846, 1.502, 0.13, 17], [0.167924, 1.139939]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-1.52, 1.198, 3.369, 2.689, 0.928, 0.07, 7], [-0.783494, 1.684604]], [[-0.056, -1.079, 26.249, 1.108, 0.441, 0.07, 8], [-0.030782, 0.966174]], [[-0.776, -0.717, 22.209, 2.822, 1.702, 0.02, 2], [-0.796827, -0.455152]], [[1.804, -0.317, 3.677, 1.295, 1.017, 0.07, 2], [1.702225, -0.959409]], [[-0.899, -1.934, 21.325, 1.527, 1.945, 0.13, 14], [-0.325493, 0.039791]], [[1.555, 1.049, 16.793, 1.993, 1.386, 0.13, 19], [0.552851, -1.240845]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.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[-1.115495, 3.511956][-1.199771, 3.497044]Failed
case 1[0.732855, -1.734106][0.763682, -1.657161]Failed
case 2[-0.104, -1.626][-0.104, -1.626]Passed
case 3[0.913071, -3.980432][1.013808, -3.889523]Failed
case 4[0.354055, -0.636021][0.36285, -0.625106]Failed
case 5[1.083, 0.787][1.083, 0.787]Passed
case 6[1.0, 0.0][1.0, 0.0]Passed
case 7[0.278786, 0.856518][0.278786, 0.856518]Passed

SHA-256 / 66d22848d800ca0f4b8736baf3cb7c6cd40041ebf0e6d6817490bf4b94330cd7

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, round(dt / 0.05))
    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.539, -1.291, 32.554, 2.072, 1.066, 0.07, 3], [-1.199771, 3.497044]], [[-1.603, 0.842, 17.725, 1.501, 1.753, 0.13, 8], [0.763682, -1.657161]], [[-0.104, -1.626, 5.159, 2.304, 0.637, 0.2, 0], [-0.104, -1.626]], [[1.295, 1.487, 26.631, 0.848, 0.944, 0.07, 2], [1.013808, -3.889523]], [[0.428, -0.176, 18.564, 2.203, 1.454, 0.07, 2], [0.36285, -0.625106]], [[1.083, 0.787, 2.365, 2.971, 0.937, 0.13, 0], [1.083, 0.787]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]]], [[[-1.643, 1.58, 13.795, 1.143, 0.679, 0.13, 8], [0.915666, -2.988323]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.925, -0.173, 39.051, 1.209, 0.422, 0.01, 5], [1.824415, -3.211948]], [[1.023, -1.573, 6.771, 1.338, 0.17, 0.07, 1], [0.895005, -1.909789]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.394, -0.949, 26.39, 2.243, 1.907, 0.13, 4], [-0.264175, -0.874209]], [[-0.479, 1.886, 38.122, 2.825, 1.885, 0.07, 7], [0.501848, 1.056904]], [[0.193, -0.706, 30.766, 2.826, 1.227, 0.07, 7], [-0.197706, -0.539247]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[0.236, -1.957, 4.07, 0.843, 0.292, 0.07, 2], [-0.039504, -1.950963]], [[1.111, 1.678, 35.58, 2.166, 0.299, 0.13, 8], [-0.758409, 2.897396]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.784, -0.426, 6.875, 0.731, 0.044, 0.1, 14], [-0.132405, 2.308875]], [[1.207, 0.351, 14.617, 2.861, 0.895, 0.07, 6], [0.802339, -1.906391]], [[1.233, -0.932, 9.854, 1.176, 1.509, 0.07, 12], [-0.56207, -0.986805]], [[-0.948, -1.497, 32.937, 1.389, 1.23, 0.07, 1], [-0.965534, 0.162831]]], [[[1.142, -1.241, 14.348, 1.124, 0.842, 0.07, 7], [-0.412514, -3.148043]], [[1.948, 0.758, 13.319, 0.711, 1.638, 0.04, 18], [-0.799165, -0.570716]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.413, 0.664, 29.552, 2.975, 1.21, 0.13, 8], [1.104015, -1.060714]], [[-0.582, -1.279, 4.833, 1.259, 1.426, 0.07, 3], [-0.756223, -0.513119]], [[0.76, 1.611, 15.529, 0.686, 0.511, 0.07, 14], [-0.242783, 2.493606]], [[0.603, 1.49, 19.896, 2.846, 1.502, 0.13, 17], [0.167924, 1.139939]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-1.52, 1.198, 3.369, 2.689, 0.928, 0.07, 7], [-0.783494, 1.684604]], [[-0.056, -1.079, 26.249, 1.108, 0.441, 0.07, 8], [-0.030782, 0.966174]], [[-0.776, -0.717, 22.209, 2.822, 1.702, 0.02, 2], [-0.796827, -0.455152]], [[1.804, -0.317, 3.677, 1.295, 1.017, 0.07, 2], [1.702225, -0.959409]], [[-0.899, -1.934, 21.325, 1.527, 1.945, 0.13, 14], [-0.325493, 0.039791]], [[1.555, 1.049, 16.793, 1.993, 1.386, 0.13, 19], [0.552851, -1.240845]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.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[-1.115495, 3.511956][-1.199771, 3.497044]Failed
case 1[0.763682, -1.657161][0.763682, -1.657161]Passed
case 2[-0.104, -1.626][-0.104, -1.626]Passed
case 3[0.913071, -3.980432][1.013808, -3.889523]Failed
case 4[0.354055, -0.636021][0.36285, -0.625106]Failed
case 5[1.083, 0.787][1.083, 0.787]Passed
case 6[1.0, 0.0][1.0, 0.0]Passed
case 7[0.278786, 0.856518][0.278786, 0.856518]Passed

SHA-256 / 805db227b27e1f40d8d05ad23f35adace55d0b2c0e784d02825faf2dddd30f21

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.539, -1.291, 32.554, 2.072, 1.066, 0.07, 3], [-1.199771, 3.497044]], [[-1.603, 0.842, 17.725, 1.501, 1.753, 0.13, 8], [0.763682, -1.657161]], [[-0.104, -1.626, 5.159, 2.304, 0.637, 0.2, 0], [-0.104, -1.626]], [[1.295, 1.487, 26.631, 0.848, 0.944, 0.07, 2], [1.013808, -3.889523]], [[0.428, -0.176, 18.564, 2.203, 1.454, 0.07, 2], [0.36285, -0.625106]], [[1.083, 0.787, 2.365, 2.971, 0.937, 0.13, 0], [1.083, 0.787]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]]], [[[-1.643, 1.58, 13.795, 1.143, 0.679, 0.13, 8], [0.915666, -2.988323]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.925, -0.173, 39.051, 1.209, 0.422, 0.01, 5], [1.824415, -3.211948]], [[1.023, -1.573, 6.771, 1.338, 0.17, 0.07, 1], [0.895005, -1.909789]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.394, -0.949, 26.39, 2.243, 1.907, 0.13, 4], [-0.264175, -0.874209]], [[-0.479, 1.886, 38.122, 2.825, 1.885, 0.07, 7], [0.501848, 1.056904]], [[0.193, -0.706, 30.766, 2.826, 1.227, 0.07, 7], [-0.197706, -0.539247]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[0.236, -1.957, 4.07, 0.843, 0.292, 0.07, 2], [-0.039504, -1.950963]], [[1.111, 1.678, 35.58, 2.166, 0.299, 0.13, 8], [-0.758409, 2.897396]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[0.784, -0.426, 6.875, 0.731, 0.044, 0.1, 14], [-0.132405, 2.308875]], [[1.207, 0.351, 14.617, 2.861, 0.895, 0.07, 6], [0.802339, -1.906391]], [[1.233, -0.932, 9.854, 1.176, 1.509, 0.07, 12], [-0.56207, -0.986805]], [[-0.948, -1.497, 32.937, 1.389, 1.23, 0.07, 1], [-0.965534, 0.162831]]], [[[1.142, -1.241, 14.348, 1.124, 0.842, 0.07, 7], [-0.412514, -3.148043]], [[1.948, 0.758, 13.319, 0.711, 1.638, 0.04, 18], [-0.799165, -0.570716]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.413, 0.664, 29.552, 2.975, 1.21, 0.13, 8], [1.104015, -1.060714]], [[-0.582, -1.279, 4.833, 1.259, 1.426, 0.07, 3], [-0.756223, -0.513119]], [[0.76, 1.611, 15.529, 0.686, 0.511, 0.07, 14], [-0.242783, 2.493606]], [[0.603, 1.49, 19.896, 2.846, 1.502, 0.13, 17], [0.167924, 1.139939]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-1.52, 1.198, 3.369, 2.689, 0.928, 0.07, 7], [-0.783494, 1.684604]], [[-0.056, -1.079, 26.249, 1.108, 0.441, 0.07, 8], [-0.030782, 0.966174]], [[-0.776, -0.717, 22.209, 2.822, 1.702, 0.02, 2], [-0.796827, -0.455152]], [[1.804, -0.317, 3.677, 1.295, 1.017, 0.07, 2], [1.702225, -0.959409]], [[-0.899, -1.934, 21.325, 1.527, 1.945, 0.13, 14], [-0.325493, 0.039791]], [[1.555, 1.049, 16.793, 1.993, 1.386, 0.13, 19], [0.552851, -1.240845]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.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[-1.199771, 3.497044][-1.199771, 3.497044]Passed
case 1[0.763682, -1.657161][0.763682, -1.657161]Passed
case 2[-0.104, -1.626][-0.104, -1.626]Passed
case 3[1.013808, -3.889523][1.013808, -3.889523]Passed
case 4[0.36285, -0.625106][0.36285, -0.625106]Passed
case 5[1.083, 0.787][1.083, 0.787]Passed
case 6[1.0, 0.0][1.0, 0.0]Passed
case 7[0.278786, 0.856518][0.278786, 0.856518]Passed

SHA-256 / 05d7dcaf02eb1283a1febf7137ef120d832235ea1e667566ba76f0d7ce59e9a7

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

Case digest / 416787d836cd3f9537c728555f5f4b0e0727cd710d100b78cf59630b90533b69