FA-86866 / Physics integrator stability / Open access
Frame loop integrates one frame too many · case 01
Even zero requested frames move the body.
ROOT CAUSE
The outer loop iterates range(steps + 1).
VERIFIED REPAIR
Iterate exactly steps frames.
Unsuccessful approach: Starting the range at one runs one frame too few.
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 + 1):
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.484, 1.712, 27.312, 2.961, 1.788, 0.13, 4], [0.446958, 3.829901]], [[1.133, -1.182, 1.342, 0.781, 1.334, 0.1, 6], [0.491288, -0.912623]], [[-1.578, 0.145, 23.597, 0.588, 1.601, 0.01, 4], [-1.511386, 2.533387]], [[-1.392, -0.154, 8.638, 2.736, 1.406, 0.02, 2], [-1.392808, 0.024118]], [[0.514, 1.143, 25.432, 0.518, 1.496, 0.13, 9], [-0.010702, -0.661591]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-1.377, 0.559, 2.87, 2.898, 1.015, 0.2, 0], [-1.377, 0.559]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]]], [[[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.526, 0.873, 6.362, 1.828, 1.137, 0.13, 0], [-1.526, 0.873]], [[1.082, 0.058, 37.566, 1.034, 1.55, 0.05, 9], [-0.710961, -1.842409]], [[1.297, -0.319, 39.855, 2.122, 0.103, 0.01, 11], [1.106036, -2.858781]], [[0.764, 1.798, 31.467, 2.606, 1.52, 0.1, 4], [0.561426, -2.076687]], [[-1.669, -1.23, 27.053, 1.533, 0.991, 0.13, 5], [1.161742, 3.128527]], [[1.602, 1.18, 24.517, 1.738, 1.228, 0.05, 12], [-0.636953, -4.398087]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]]], [[[0.718, 1.584, 9.356, 1.657, 0.286, 0.02, 6], [0.869722, 1.022798]], [[0.784, -0.426, 6.875, 0.731, 0.044, 0.1, 14], [-0.132405, 2.308875]], [[-1.544, -1.79, 32.966, 0.813, 1.455, 0.13, 4], [0.965451, -0.257038]], [[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]], [[0.044, 1.377, 37.377, 1.154, 1.523, 0.13, 8], [-0.013801, 0.684737]], [[-1.525, 1.375, 3.248, 1.693, 1.607, 0.1, 4], [-0.87326, 1.732745]], [[-1.184, -1.519, 7.411, 1.786, 1.332, 0.04, 9], [-1.302744, 0.571925]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.818, 0.29, 35.382, 2.651, 1.322, 0.04, 12], [-0.242082, -5.8862]], [[-0.271, 1.727, 34.523, 2.128, 1.589, 0.13, 15], [0.205585, 0.49101]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.56, 1.419, 29.61, 1.024, 1.85, 0.1, 6], [0.298786, -1.014037]], [[1.308, -0.567, 4.917, 1.142, 0.149, 0.02, 14], [0.933908, -1.927012]], [[-0.362, 1.363, 36.638, 0.869, 0.886, 0.2, 7], [0.193298, -0.396669]], [[0.484, 0.239, 12.355, 0.868, 0.3, 0.07, 0], [0.484, 0.239]]], [[[-1.576, -0.203, 24.72, 2.347, 1.363, 0.07, 10], [0.760633, 3.318988]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-0.232, 1.993, 10.835, 2.4, 1.522, 0.01, 9], [-0.054651, 1.942283]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.68, 0.833, 26.799, 1.025, 1.524, 0.13, 6], [0.548391, -4.033319]], [[-1.38, 1.106, 38.807, 2.361, 0.186, 0.05, 14], [1.397704, 0.597706]], [[0.695, 1.714, 8.192, 1.726, 1.775, 0.02, 2], [0.757467, 1.510568]], [[1.759, -0.492, 32.001, 1.805, 1.669, 0.13, 5], [-1.219998, -1.776701]]]]
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.863255, 2.837862] | [0.446958, 3.829901] | Failed |
| case 1 | [0.405421, -0.840444] | [0.491288, -0.912623] | Failed |
| case 2 | [-1.480676, 3.070942] | [-1.511386, 2.533387] | Failed |
| case 3 | [-1.390572, 0.111817] | [-1.392808, 0.024118] | Failed |
| case 4 | [-0.063446, -0.248749] | [-0.010702, -0.661591] | Failed |
| case 5 | [0.358469, 0.775741] | [0.278786, 0.856518] | Failed |
| case 6 | [-1.237301, 0.776909] | [-1.377, 0.559] | Failed |
| case 7 | [0.975, -0.5] | [1.0, 0.0] | Failed |
SHA-256 / d80dba52bccbea2ab1b71d9bc12eeb1f5a16ad3bb1a24ab028e78d8b7d1945c0
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(1, 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.484, 1.712, 27.312, 2.961, 1.788, 0.13, 4], [0.446958, 3.829901]], [[1.133, -1.182, 1.342, 0.781, 1.334, 0.1, 6], [0.491288, -0.912623]], [[-1.578, 0.145, 23.597, 0.588, 1.601, 0.01, 4], [-1.511386, 2.533387]], [[-1.392, -0.154, 8.638, 2.736, 1.406, 0.02, 2], [-1.392808, 0.024118]], [[0.514, 1.143, 25.432, 0.518, 1.496, 0.13, 9], [-0.010702, -0.661591]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-1.377, 0.559, 2.87, 2.898, 1.015, 0.2, 0], [-1.377, 0.559]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]]], [[[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.526, 0.873, 6.362, 1.828, 1.137, 0.13, 0], [-1.526, 0.873]], [[1.082, 0.058, 37.566, 1.034, 1.55, 0.05, 9], [-0.710961, -1.842409]], [[1.297, -0.319, 39.855, 2.122, 0.103, 0.01, 11], [1.106036, -2.858781]], [[0.764, 1.798, 31.467, 2.606, 1.52, 0.1, 4], [0.561426, -2.076687]], [[-1.669, -1.23, 27.053, 1.533, 0.991, 0.13, 5], [1.161742, 3.128527]], [[1.602, 1.18, 24.517, 1.738, 1.228, 0.05, 12], [-0.636953, -4.398087]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]]], [[[0.718, 1.584, 9.356, 1.657, 0.286, 0.02, 6], [0.869722, 1.022798]], [[0.784, -0.426, 6.875, 0.731, 0.044, 0.1, 14], [-0.132405, 2.308875]], [[-1.544, -1.79, 32.966, 0.813, 1.455, 0.13, 4], [0.965451, -0.257038]], [[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]], [[0.044, 1.377, 37.377, 1.154, 1.523, 0.13, 8], [-0.013801, 0.684737]], [[-1.525, 1.375, 3.248, 1.693, 1.607, 0.1, 4], [-0.87326, 1.732745]], [[-1.184, -1.519, 7.411, 1.786, 1.332, 0.04, 9], [-1.302744, 0.571925]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.818, 0.29, 35.382, 2.651, 1.322, 0.04, 12], [-0.242082, -5.8862]], [[-0.271, 1.727, 34.523, 2.128, 1.589, 0.13, 15], [0.205585, 0.49101]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.56, 1.419, 29.61, 1.024, 1.85, 0.1, 6], [0.298786, -1.014037]], [[1.308, -0.567, 4.917, 1.142, 0.149, 0.02, 14], [0.933908, -1.927012]], [[-0.362, 1.363, 36.638, 0.869, 0.886, 0.2, 7], [0.193298, -0.396669]], [[0.484, 0.239, 12.355, 0.868, 0.3, 0.07, 0], [0.484, 0.239]]], [[[-1.576, -0.203, 24.72, 2.347, 1.363, 0.07, 10], [0.760633, 3.318988]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-0.232, 1.993, 10.835, 2.4, 1.522, 0.01, 9], [-0.054651, 1.942283]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.68, 0.833, 26.799, 1.025, 1.524, 0.13, 6], [0.548391, -4.033319]], [[-1.38, 1.106, 38.807, 2.361, 0.186, 0.05, 14], [1.397704, 0.597706]], [[0.695, 1.714, 8.192, 1.726, 1.775, 0.02, 2], [0.757467, 1.510568]], [[1.759, -0.492, 32.001, 1.805, 1.669, 0.13, 5], [-1.219998, -1.776701]]]]
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.076434, 4.281738] | [0.446958, 3.829901] | Failed |
| case 1 | [0.58429, -0.980985] | [0.491288, -0.912623] | Failed |
| case 2 | [-1.53672, 1.970335] | [-1.511386, 2.533387] | Failed |
| case 3 | [-1.39329, -0.064522] | [-1.392808, 0.024118] | Failed |
| case 4 | [0.07899, -0.603109] | [-0.010702, -0.661591] | Failed |
| case 5 | [0.191397, 0.921919] | [0.278786, 0.856518] | Failed |
| case 6 | [-1.377, 0.559] | [-1.377, 0.559] | Passed |
| case 7 | [1.0, 0.0] | [1.0, 0.0] | Passed |
SHA-256 / 2311afed003cf886fd81a6d60e509236af2cd7c9bd16671bbda98d40ad959829
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.484, 1.712, 27.312, 2.961, 1.788, 0.13, 4], [0.446958, 3.829901]], [[1.133, -1.182, 1.342, 0.781, 1.334, 0.1, 6], [0.491288, -0.912623]], [[-1.578, 0.145, 23.597, 0.588, 1.601, 0.01, 4], [-1.511386, 2.533387]], [[-1.392, -0.154, 8.638, 2.736, 1.406, 0.02, 2], [-1.392808, 0.024118]], [[0.514, 1.143, 25.432, 0.518, 1.496, 0.13, 9], [-0.010702, -0.661591]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-1.377, 0.559, 2.87, 2.898, 1.015, 0.2, 0], [-1.377, 0.559]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]]], [[[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.526, 0.873, 6.362, 1.828, 1.137, 0.13, 0], [-1.526, 0.873]], [[1.082, 0.058, 37.566, 1.034, 1.55, 0.05, 9], [-0.710961, -1.842409]], [[1.297, -0.319, 39.855, 2.122, 0.103, 0.01, 11], [1.106036, -2.858781]], [[0.764, 1.798, 31.467, 2.606, 1.52, 0.1, 4], [0.561426, -2.076687]], [[-1.669, -1.23, 27.053, 1.533, 0.991, 0.13, 5], [1.161742, 3.128527]], [[1.602, 1.18, 24.517, 1.738, 1.228, 0.05, 12], [-0.636953, -4.398087]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]]], [[[0.718, 1.584, 9.356, 1.657, 0.286, 0.02, 6], [0.869722, 1.022798]], [[0.784, -0.426, 6.875, 0.731, 0.044, 0.1, 14], [-0.132405, 2.308875]], [[-1.544, -1.79, 32.966, 0.813, 1.455, 0.13, 4], [0.965451, -0.257038]], [[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]], [[0.044, 1.377, 37.377, 1.154, 1.523, 0.13, 8], [-0.013801, 0.684737]], [[-1.525, 1.375, 3.248, 1.693, 1.607, 0.1, 4], [-0.87326, 1.732745]], [[-1.184, -1.519, 7.411, 1.786, 1.332, 0.04, 9], [-1.302744, 0.571925]]], [[[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[1.818, 0.29, 35.382, 2.651, 1.322, 0.04, 12], [-0.242082, -5.8862]], [[-0.271, 1.727, 34.523, 2.128, 1.589, 0.13, 15], [0.205585, 0.49101]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-0.56, 1.419, 29.61, 1.024, 1.85, 0.1, 6], [0.298786, -1.014037]], [[1.308, -0.567, 4.917, 1.142, 0.149, 0.02, 14], [0.933908, -1.927012]], [[-0.362, 1.363, 36.638, 0.869, 0.886, 0.2, 7], [0.193298, -0.396669]], [[0.484, 0.239, 12.355, 0.868, 0.3, 0.07, 0], [0.484, 0.239]]], [[[-1.576, -0.203, 24.72, 2.347, 1.363, 0.07, 10], [0.760633, 3.318988]], [[0.0, 1.0, 4.0, 2.0, 0.5, 0.1, 3], [0.278786, 0.856518]], [[-0.232, 1.993, 10.835, 2.4, 1.522, 0.01, 9], [-0.054651, 1.942283]], [[1.0, 0.0, 10.0, 1.0, 0.0, 0.05, 0], [1.0, 0.0]], [[-1.68, 0.833, 26.799, 1.025, 1.524, 0.13, 6], [0.548391, -4.033319]], [[-1.38, 1.106, 38.807, 2.361, 0.186, 0.05, 14], [1.397704, 0.597706]], [[0.695, 1.714, 8.192, 1.726, 1.775, 0.02, 2], [0.757467, 1.510568]], [[1.759, -0.492, 32.001, 1.805, 1.669, 0.13, 5], [-1.219998, -1.776701]]]]
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.446958, 3.829901] | [0.446958, 3.829901] | Passed |
| case 1 | [0.491288, -0.912623] | [0.491288, -0.912623] | Passed |
| case 2 | [-1.511386, 2.533387] | [-1.511386, 2.533387] | Passed |
| case 3 | [-1.392808, 0.024118] | [-1.392808, 0.024118] | Passed |
| case 4 | [-0.010702, -0.661591] | [-0.010702, -0.661591] | Passed |
| case 5 | [0.278786, 0.856518] | [0.278786, 0.856518] | Passed |
| case 6 | [-1.377, 0.559] | [-1.377, 0.559] | Passed |
| case 7 | [1.0, 0.0] | [1.0, 0.0] | Passed |
SHA-256 / c24f638da77269b6879e30af6cbe467eee10eb6d351b62e4b45608743fc668bd
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.460123+00:00.
Case digest / d720cd11c91c60405be7db202e1cf75801fdf1055d28efca68c674071fcf6520