FA-87141 / Physics integrator stability / Open access
Current and previous step lengths are swapped · case 01
Accelerating or decelerating frame rates distort the trajectory in opposite directions.
ROOT CAUSE
h and h_prev are read as dts[i-1] and dts[i].
VERIFIED REPAIR
The step being taken is dts[i]; the previous one is dts[i-1].
Unsuccessful approach: Using the first step as the permanent previous step fails after several changes.
Case contract
solve(x0, v0, w2, g, dts): position Verlet with variable steps, acceleration -w2*x+g. First step is Taylor x1=x0+v0*h0+0.5*a(x0)*h0^2; then x_{n+1}=x_n+(x_n-x_{n-1})*(h_n/h_{n-1})+a(x_n)*h_n*(h_n+h_{n-1})/2. Velocity estimate is (x_n-x_{n-1})/h_last. Empty dts returns the initial state. Return [x, v] rounded.
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, w2, g, dts):
def acc(p):
return -w2 * p + g
if not dts:
return [round(x0, 6), round(v0, 6)]
prev = x0
x = x0 + v0 * dts[0] + 0.5 * acc(x0) * dts[0] * dts[0]
for i in range(1, len(dts)):
h, hp = dts[i - 1], dts[i]
nxt = x + (x - prev) * (h / hp) + acc(x) * h * (h + hp) / 2
prev, x = x, nxt
v = (x - prev) / dts[-1]
return [round(x, 6), round(v, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.159, 1.403, 12.469, 0.0, [0.01, 0.02, 0.05]], [0.263816, 1.273396]], [[0.78, -0.691, 5.2, -9.81, [0.01, 0.05, 0.05]], [0.621082, -1.85117]], [[-1.619, -0.338, 11.211, 0.0, [0.03, 0.01, 0.02, 0.01]], [-1.598199, 0.839977]], [[1.835, -1.641, 6.597, 2.0, [0.02, 0.02]], [1.761368, -1.939568]], [[1.699, 1.89, 11.648, 2.0, [0.01, 0.01, 0.01]], [1.747613, 1.439167]], [[-1.409, 0.693, 7.348, -9.81, [0.02, 0.02, 0.05, 0.05, 0.05, 0.01]], [-1.266157, 0.69937]], [[-1.118, 1.653, 18.431, 0.0, [0.03, 0.03]], [-0.982706, 2.547717]], [[1.52, -0.79, 13.208, 2.0, [0.03, 0.03, 0.02, 0.02, 0.05, 0.05]], [1.02811, -3.599677]]], [[[1.821, 1.198, 3.771, 0.0, [0.02, 0.01, 0.05, 0.02, 0.01]], [1.910483, 0.457456]], [[0.644, -0.151, 13.871, -9.81, [0.02, 0.02, 0.01, 0.01, 0.05, 0.02, 0.03]], [0.387894, -2.717601]], [[-1.926, 1.549, 6.213, -9.81, [0.01, 0.03, 0.01, 0.02, 0.02]], [-1.778987, 1.690133]], [[-0.485, 1.456, 18.965, 2.0, [0.01, 0.03, 0.05, 0.05, 0.02, 0.02]], [-0.07504, 2.805631]], [[1.693, 1.184, 10.982, -9.81, [0.02, 0.03, 0.05, 0.05]], [1.550068, -2.368135]], [[0.454, 0.048, 11.925, 0.0, [0.01, 0.01, 0.01]], [0.453003, -0.087359]], [[0.426, -0.985, 9.249, -9.81, [0.02, 0.05, 0.05]], [0.211891, -2.236693]], [[-1.855, 0.692, 12.133, 2.0, [0.02, 0.01, 0.01, 0.03]], [-1.747187, 2.021157]]], [[[1.462, -1.373, 3.135, 0.0, [0.05, 0.02, 0.02, 0.01, 0.02, 0.05, 0.03]], [1.102151, -2.133217]], [[-1.478, -0.428, 14.024, 2.0, [0.01, 0.02, 0.01]], [-1.476913, 0.36867]], [[1.606, 1.967, 9.66, 0.0, [0.05, 0.03, 0.01, 0.02, 0.03, 0.05, 0.01]], [1.674706, -1.223548]], [[-0.31, -1.257, 5.955, 0.0, [0.05, 0.03, 0.05]], [-0.455535, -1.0263]], [[-0.686, 1.111, 17.91, -9.81, [0.05, 0.03, 0.03, 0.03]], [-0.5152, 1.255652]], [[0.106, -1.937, 2.608, 0.0, [0.03, 0.01, 0.03, 0.05, 0.01, 0.05, 0.05, 0.02]], [-0.374085, -1.858207]], [[0.68, 1.896, 12.013, -9.81, [0.03, 0.03, 0.03]], [0.775896, 0.500859]], [[0.152, -1.091, 14.043, 0.0, [0.01, 0.02, 0.01, 0.02, 0.05, 0.01]], [0.009941, -1.229064]]], [[[-1.698, 0.325, 6.546, -9.81, [0.02, 0.02]], [-1.683974, 0.363268]], [[0.382, 0.792, 11.215, 0.0, [0.05, 0.01, 0.02, 0.05, 0.05, 0.03, 0.05, 0.03]], [0.411226, -0.538349]], [[-0.101, 1.196, 2.452, -9.81, [0.03, 0.02, 0.01, 0.03, 0.03, 0.02, 0.03, 0.01, 0.01]], [-0.048386, -0.598008]], [[-0.101, -1.02, 19.226, 2.0, [0.05, 0.01]], [-0.154839, -0.776626]], [[1.286, 1.196, 2.402, 0.0, [0.05, 0.05, 0.02, 0.01, 0.05, 0.05, 0.05, 0.03, 0.05]], [1.499807, 0.049865]], [[1.484, 1.78, 2.899, 2.0, [0.01, 0.02, 0.05, 0.01, 0.03]], [1.679732, 1.511762]], [[1.406, -0.862, 9.145, 0.0, [0.03, 0.01, 0.01, 0.03, 0.01, 0.01]], [1.257218, -2.031082]], [[-0.434, -1.688, 15.899, 0.0, [0.03, 0.05, 0.01, 0.02]], [-0.573341, -0.885589]]], [[[-0.841, -0.714, 0.875, 2.0, [0.05, 0.02, 0.02, 0.01, 0.01, 0.02, 0.02, 0.01]], [-0.919878, -0.283965]], [[-1.17, -1.869, 6.983, 2.0, [0.02, 0.02, 0.01, 0.01, 0.01, 0.03]], [-1.304253, -0.965785]], [[-0.421, -1.461, 6.131, 2.0, [0.02, 0.01, 0.03, 0.05, 0.01, 0.05]], [-0.597191, -0.719646]], [[0.363, 0.71, 3.317, 0.0, [0.01, 0.02]], [0.383751, 0.685568]], [[1.217, -0.953, 5.249, -9.81, [0.02, 0.01]], [1.181138, -1.356195]], [[1.94, 0.689, 18.476, 0.0, [0.05, 0.01, 0.03, 0.01, 0.05, 0.02, 0.03, 0.02, 0.02]], [1.133627, -6.57541]], [[1.097, 0.642, 16.162, 2.0, [0.01, 0.03]], [1.110042, 0.246936]], [[-1.434, -0.348, 10.741, -9.81, [0.03, 0.03, 0.03, 0.01, 0.05, 0.03, 0.03, 0.03, 0.01, 0.01]], [-1.336072, 1.031162]]]]
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.180662, 0.021789] | [0.263816, 1.273396] | Failed |
| case 1 | [0.726567, -0.803221] | [0.621082, -1.85117] | Failed |
| case 2 | [-1.594935, 1.583951] | [-1.598199, 0.839977] | Failed |
| case 3 | [1.761368, -1.939568] | [1.761368, -1.939568] | Passed |
| case 4 | [1.747613, 1.439167] | [1.747613, 1.439167] | Passed |
| case 5 | [-1.324643, 3.648329] | [-1.266157, 0.69937] | Failed |
| case 6 | [-0.982706, 2.547717] | [-0.982706, 2.547717] | Passed |
| case 7 | [1.136947, -1.636623] | [1.02811, -3.599677] | Failed |
SHA-256 / 2675a869d65db3814cbebb33bbc6545fc8025e586ecf3c0081236dedb9acaf7f
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, w2, g, dts):
def acc(p):
return -w2 * p + g
if not dts:
return [round(x0, 6), round(v0, 6)]
prev = x0
x = x0 + v0 * dts[0] + 0.5 * acc(x0) * dts[0] * dts[0]
for i in range(1, len(dts)):
h, hp = dts[i], dts[0]
nxt = x + (x - prev) * (h / hp) + acc(x) * h * (h + hp) / 2
prev, x = x, nxt
v = (x - prev) / dts[-1]
return [round(x, 6), round(v, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.159, 1.403, 12.469, 0.0, [0.01, 0.02, 0.05]], [0.263816, 1.273396]], [[0.78, -0.691, 5.2, -9.81, [0.01, 0.05, 0.05]], [0.621082, -1.85117]], [[-1.619, -0.338, 11.211, 0.0, [0.03, 0.01, 0.02, 0.01]], [-1.598199, 0.839977]], [[1.835, -1.641, 6.597, 2.0, [0.02, 0.02]], [1.761368, -1.939568]], [[1.699, 1.89, 11.648, 2.0, [0.01, 0.01, 0.01]], [1.747613, 1.439167]], [[-1.409, 0.693, 7.348, -9.81, [0.02, 0.02, 0.05, 0.05, 0.05, 0.01]], [-1.266157, 0.69937]], [[-1.118, 1.653, 18.431, 0.0, [0.03, 0.03]], [-0.982706, 2.547717]], [[1.52, -0.79, 13.208, 2.0, [0.03, 0.03, 0.02, 0.02, 0.05, 0.05]], [1.02811, -3.599677]]], [[[1.821, 1.198, 3.771, 0.0, [0.02, 0.01, 0.05, 0.02, 0.01]], [1.910483, 0.457456]], [[0.644, -0.151, 13.871, -9.81, [0.02, 0.02, 0.01, 0.01, 0.05, 0.02, 0.03]], [0.387894, -2.717601]], [[-1.926, 1.549, 6.213, -9.81, [0.01, 0.03, 0.01, 0.02, 0.02]], [-1.778987, 1.690133]], [[-0.485, 1.456, 18.965, 2.0, [0.01, 0.03, 0.05, 0.05, 0.02, 0.02]], [-0.07504, 2.805631]], [[1.693, 1.184, 10.982, -9.81, [0.02, 0.03, 0.05, 0.05]], [1.550068, -2.368135]], [[0.454, 0.048, 11.925, 0.0, [0.01, 0.01, 0.01]], [0.453003, -0.087359]], [[0.426, -0.985, 9.249, -9.81, [0.02, 0.05, 0.05]], [0.211891, -2.236693]], [[-1.855, 0.692, 12.133, 2.0, [0.02, 0.01, 0.01, 0.03]], [-1.747187, 2.021157]]], [[[1.462, -1.373, 3.135, 0.0, [0.05, 0.02, 0.02, 0.01, 0.02, 0.05, 0.03]], [1.102151, -2.133217]], [[-1.478, -0.428, 14.024, 2.0, [0.01, 0.02, 0.01]], [-1.476913, 0.36867]], [[1.606, 1.967, 9.66, 0.0, [0.05, 0.03, 0.01, 0.02, 0.03, 0.05, 0.01]], [1.674706, -1.223548]], [[-0.31, -1.257, 5.955, 0.0, [0.05, 0.03, 0.05]], [-0.455535, -1.0263]], [[-0.686, 1.111, 17.91, -9.81, [0.05, 0.03, 0.03, 0.03]], [-0.5152, 1.255652]], [[0.106, -1.937, 2.608, 0.0, [0.03, 0.01, 0.03, 0.05, 0.01, 0.05, 0.05, 0.02]], [-0.374085, -1.858207]], [[0.68, 1.896, 12.013, -9.81, [0.03, 0.03, 0.03]], [0.775896, 0.500859]], [[0.152, -1.091, 14.043, 0.0, [0.01, 0.02, 0.01, 0.02, 0.05, 0.01]], [0.009941, -1.229064]]], [[[-1.698, 0.325, 6.546, -9.81, [0.02, 0.02]], [-1.683974, 0.363268]], [[0.382, 0.792, 11.215, 0.0, [0.05, 0.01, 0.02, 0.05, 0.05, 0.03, 0.05, 0.03]], [0.411226, -0.538349]], [[-0.101, 1.196, 2.452, -9.81, [0.03, 0.02, 0.01, 0.03, 0.03, 0.02, 0.03, 0.01, 0.01]], [-0.048386, -0.598008]], [[-0.101, -1.02, 19.226, 2.0, [0.05, 0.01]], [-0.154839, -0.776626]], [[1.286, 1.196, 2.402, 0.0, [0.05, 0.05, 0.02, 0.01, 0.05, 0.05, 0.05, 0.03, 0.05]], [1.499807, 0.049865]], [[1.484, 1.78, 2.899, 2.0, [0.01, 0.02, 0.05, 0.01, 0.03]], [1.679732, 1.511762]], [[1.406, -0.862, 9.145, 0.0, [0.03, 0.01, 0.01, 0.03, 0.01, 0.01]], [1.257218, -2.031082]], [[-0.434, -1.688, 15.899, 0.0, [0.03, 0.05, 0.01, 0.02]], [-0.573341, -0.885589]]], [[[-0.841, -0.714, 0.875, 2.0, [0.05, 0.02, 0.02, 0.01, 0.01, 0.02, 0.02, 0.01]], [-0.919878, -0.283965]], [[-1.17, -1.869, 6.983, 2.0, [0.02, 0.02, 0.01, 0.01, 0.01, 0.03]], [-1.304253, -0.965785]], [[-0.421, -1.461, 6.131, 2.0, [0.02, 0.01, 0.03, 0.05, 0.01, 0.05]], [-0.597191, -0.719646]], [[0.363, 0.71, 3.317, 0.0, [0.01, 0.02]], [0.383751, 0.685568]], [[1.217, -0.953, 5.249, -9.81, [0.02, 0.01]], [1.181138, -1.356195]], [[1.94, 0.689, 18.476, 0.0, [0.05, 0.01, 0.03, 0.01, 0.05, 0.02, 0.03, 0.02, 0.02]], [1.133627, -6.57541]], [[1.097, 0.642, 16.162, 2.0, [0.01, 0.03]], [1.110042, 0.246936]], [[-1.434, -0.348, 10.741, -9.81, [0.03, 0.03, 0.03, 0.01, 0.05, 0.03, 0.03, 0.03, 0.01, 0.01]], [-1.336072, 1.031162]]]]
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.332477, 2.646618] | [0.263816, 1.273396] | Failed |
| case 1 | [0.399578, -6.281247] | [0.621082, -1.85117] | Failed |
| case 2 | [-1.599653, 0.728789] | [-1.598199, 0.839977] | Failed |
| case 3 | [1.761368, -1.939568] | [1.761368, -1.939568] | Passed |
| case 4 | [1.747613, 1.439167] | [1.747613, 1.439167] | Passed |
| case 5 | [-0.919286, 11.165737] | [-1.266157, 0.69937] | Failed |
| case 6 | [-0.982706, 2.547717] | [-0.982706, 2.547717] | Passed |
| case 7 | [1.093687, -3.613646] | [1.02811, -3.599677] | Failed |
SHA-256 / d47583ccbd1d87f52bce2ea46672771c2e6c1535c8b84493481769caebe52f3b
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, w2, g, dts):
def acc(p):
return -w2 * p + g
if not dts:
return [round(x0, 6), round(v0, 6)]
prev = x0
x = x0 + v0 * dts[0] + 0.5 * acc(x0) * dts[0] * dts[0]
for i in range(1, len(dts)):
h, hp = dts[i], dts[i - 1]
nxt = x + (x - prev) * (h / hp) + acc(x) * h * (h + hp) / 2
prev, x = x, nxt
v = (x - prev) / dts[-1]
return [round(x, 6), round(v, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[0.159, 1.403, 12.469, 0.0, [0.01, 0.02, 0.05]], [0.263816, 1.273396]], [[0.78, -0.691, 5.2, -9.81, [0.01, 0.05, 0.05]], [0.621082, -1.85117]], [[-1.619, -0.338, 11.211, 0.0, [0.03, 0.01, 0.02, 0.01]], [-1.598199, 0.839977]], [[1.835, -1.641, 6.597, 2.0, [0.02, 0.02]], [1.761368, -1.939568]], [[1.699, 1.89, 11.648, 2.0, [0.01, 0.01, 0.01]], [1.747613, 1.439167]], [[-1.409, 0.693, 7.348, -9.81, [0.02, 0.02, 0.05, 0.05, 0.05, 0.01]], [-1.266157, 0.69937]], [[-1.118, 1.653, 18.431, 0.0, [0.03, 0.03]], [-0.982706, 2.547717]], [[1.52, -0.79, 13.208, 2.0, [0.03, 0.03, 0.02, 0.02, 0.05, 0.05]], [1.02811, -3.599677]]], [[[1.821, 1.198, 3.771, 0.0, [0.02, 0.01, 0.05, 0.02, 0.01]], [1.910483, 0.457456]], [[0.644, -0.151, 13.871, -9.81, [0.02, 0.02, 0.01, 0.01, 0.05, 0.02, 0.03]], [0.387894, -2.717601]], [[-1.926, 1.549, 6.213, -9.81, [0.01, 0.03, 0.01, 0.02, 0.02]], [-1.778987, 1.690133]], [[-0.485, 1.456, 18.965, 2.0, [0.01, 0.03, 0.05, 0.05, 0.02, 0.02]], [-0.07504, 2.805631]], [[1.693, 1.184, 10.982, -9.81, [0.02, 0.03, 0.05, 0.05]], [1.550068, -2.368135]], [[0.454, 0.048, 11.925, 0.0, [0.01, 0.01, 0.01]], [0.453003, -0.087359]], [[0.426, -0.985, 9.249, -9.81, [0.02, 0.05, 0.05]], [0.211891, -2.236693]], [[-1.855, 0.692, 12.133, 2.0, [0.02, 0.01, 0.01, 0.03]], [-1.747187, 2.021157]]], [[[1.462, -1.373, 3.135, 0.0, [0.05, 0.02, 0.02, 0.01, 0.02, 0.05, 0.03]], [1.102151, -2.133217]], [[-1.478, -0.428, 14.024, 2.0, [0.01, 0.02, 0.01]], [-1.476913, 0.36867]], [[1.606, 1.967, 9.66, 0.0, [0.05, 0.03, 0.01, 0.02, 0.03, 0.05, 0.01]], [1.674706, -1.223548]], [[-0.31, -1.257, 5.955, 0.0, [0.05, 0.03, 0.05]], [-0.455535, -1.0263]], [[-0.686, 1.111, 17.91, -9.81, [0.05, 0.03, 0.03, 0.03]], [-0.5152, 1.255652]], [[0.106, -1.937, 2.608, 0.0, [0.03, 0.01, 0.03, 0.05, 0.01, 0.05, 0.05, 0.02]], [-0.374085, -1.858207]], [[0.68, 1.896, 12.013, -9.81, [0.03, 0.03, 0.03]], [0.775896, 0.500859]], [[0.152, -1.091, 14.043, 0.0, [0.01, 0.02, 0.01, 0.02, 0.05, 0.01]], [0.009941, -1.229064]]], [[[-1.698, 0.325, 6.546, -9.81, [0.02, 0.02]], [-1.683974, 0.363268]], [[0.382, 0.792, 11.215, 0.0, [0.05, 0.01, 0.02, 0.05, 0.05, 0.03, 0.05, 0.03]], [0.411226, -0.538349]], [[-0.101, 1.196, 2.452, -9.81, [0.03, 0.02, 0.01, 0.03, 0.03, 0.02, 0.03, 0.01, 0.01]], [-0.048386, -0.598008]], [[-0.101, -1.02, 19.226, 2.0, [0.05, 0.01]], [-0.154839, -0.776626]], [[1.286, 1.196, 2.402, 0.0, [0.05, 0.05, 0.02, 0.01, 0.05, 0.05, 0.05, 0.03, 0.05]], [1.499807, 0.049865]], [[1.484, 1.78, 2.899, 2.0, [0.01, 0.02, 0.05, 0.01, 0.03]], [1.679732, 1.511762]], [[1.406, -0.862, 9.145, 0.0, [0.03, 0.01, 0.01, 0.03, 0.01, 0.01]], [1.257218, -2.031082]], [[-0.434, -1.688, 15.899, 0.0, [0.03, 0.05, 0.01, 0.02]], [-0.573341, -0.885589]]], [[[-0.841, -0.714, 0.875, 2.0, [0.05, 0.02, 0.02, 0.01, 0.01, 0.02, 0.02, 0.01]], [-0.919878, -0.283965]], [[-1.17, -1.869, 6.983, 2.0, [0.02, 0.02, 0.01, 0.01, 0.01, 0.03]], [-1.304253, -0.965785]], [[-0.421, -1.461, 6.131, 2.0, [0.02, 0.01, 0.03, 0.05, 0.01, 0.05]], [-0.597191, -0.719646]], [[0.363, 0.71, 3.317, 0.0, [0.01, 0.02]], [0.383751, 0.685568]], [[1.217, -0.953, 5.249, -9.81, [0.02, 0.01]], [1.181138, -1.356195]], [[1.94, 0.689, 18.476, 0.0, [0.05, 0.01, 0.03, 0.01, 0.05, 0.02, 0.03, 0.02, 0.02]], [1.133627, -6.57541]], [[1.097, 0.642, 16.162, 2.0, [0.01, 0.03]], [1.110042, 0.246936]], [[-1.434, -0.348, 10.741, -9.81, [0.03, 0.03, 0.03, 0.01, 0.05, 0.03, 0.03, 0.03, 0.01, 0.01]], [-1.336072, 1.031162]]]]
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.263816, 1.273396] | [0.263816, 1.273396] | Passed |
| case 1 | [0.621082, -1.85117] | [0.621082, -1.85117] | Passed |
| case 2 | [-1.598199, 0.839977] | [-1.598199, 0.839977] | Passed |
| case 3 | [1.761368, -1.939568] | [1.761368, -1.939568] | Passed |
| case 4 | [1.747613, 1.439167] | [1.747613, 1.439167] | Passed |
| case 5 | [-1.266157, 0.69937] | [-1.266157, 0.69937] | Passed |
| case 6 | [-0.982706, 2.547717] | [-0.982706, 2.547717] | Passed |
| case 7 | [1.02811, -3.599677] | [1.02811, -3.599677] | Passed |
SHA-256 / 7bcd2586e5b1f9442b5f074d07072332dafbef201ea1b471eca080ee68a41776
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.996410+00:00.
Case digest / 6774b1cd246932f5cba99593046fb3d53f4fda99298e5807f8c827c988610843