FAILURE MAP
← Case archive

FA-86936 / Physics integrator stability / Open access

Springs attached to a static anchor are ignored when limiting the step · case 01

Stiff tethers to the world explode because they never shrink the substep.

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

ROOT CAUSE

Any spring with a static endpoint is skipped instead of only springs with two static endpoints.

VERIFIED REPAIR

Skip only when the summed inverse mass is zero.

Unsuccessful approach: Skipping when the first endpoint is static still ignores half of the anchored springs.

Case contract

solve(springs, safety, dt_frame, max_sub): springs are [k, inv_mass_a, inv_mass_b] (inverse mass 0 = static). A spring with k>0 and nonzero summed inverse mass w limits the substep to safety*2/sqrt(k*w). n=ceil(dt_frame/h_min) clamped to [1,max_sub]; return [n, dt_frame/n rounded to 6, whether n had to be clamped down]. No limiting spring gives [1, dt_frame, False].

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(springs, safety, dt_frame, max_sub):
    hc = None
    for k, im1, im2 in springs:
        w = im1 + im2
        if im1 == 0 or im2 == 0 or k <= 0:
            continue
        h = safety * 2.0 / math.sqrt(k * w)
        if hc is None or h < hc:
            hc = h
    if hc is None:
        return [1, round(dt_frame, 6), False]
    n = math.ceil(dt_frame / hc - 1e-12)
    clamped = n > max_sub
    n = min(max(n, 1), max_sub)
    return [n, round(dt_frame / n, 6), clamped]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[[[39.9, 0.852, 0.112]], 0.9, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[167.6, 0.0, 1.769], [238.7, 0.0, 1.824], [101.3, 0.949, 0.0], [328.1, 0.0, 1.65]], 0.9, 0.1, 10], [2, 0.05, False]], [[[[51.4, 0.26, 0.0], [194.4, 1.517, 0.0]], 0.5, 0.1, 5], [2, 0.05, False]], [[[[172.3, 1.054, 0.0]], 0.5, 0.1, 12], [2, 0.05, False]], [[[[217.3, 0.0, 0.272], [226.4, 1.256, 1.506], [86.4, 1.936, 1.121]], 0.8, 0.1, 7], [2, 0.05, False]], [[[[106.5, 1.63, 0.0]], 0.9, 0.016666666666666666, 2], [1, 0.016667, False]], [[[[0.0, 0.185, 0.0], [369.7, 0.0, 1.891], [271.6, 0.712, 0.0], [0.0, 0.0, 0.0]], 0.9, 0.05, 5], [1, 0.05, False]], [[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]]], [[[[[239.0, 0.421, 0.398], [322.8, 0.0, 1.917], [189.8, 0.0, 0.0], [229.0, 1.496, 0.0]], 0.5, 0.05, 9], [2, 0.025, False]], [[[[329.8, 0.384, 1.012]], 0.5, 0.03333333333333333, 12], [1, 0.033333, False]], [[[[225.1, 0.0, 1.023]], 0.5, 0.016666666666666666, 4], [1, 0.016667, False]], [[[[254.1, 0.546, 0.0], [148.9, 0.748, 0.0], [149.5, 0.0, 0.0]], 0.5, 0.05, 1], [1, 0.05, False]], [[[[246.1, 0.0, 1.235]], 0.9, 0.1, 5], [1, 0.1, False]], [[[[68.2, 0.0, 1.77], [298.4, 1.344, 1.263]], 0.8, 0.05, 2], [1, 0.05, False]], [[[[397.6, 1.504, 0.0], [245.5, 1.813, 0.706], [252.0, 0.608, 0.796]], 0.5, 0.1, 9], [3, 0.033333, False]], [[[[356.6, 1.026, 0.0], [52.1, 1.266, 1.354]], 0.8, 0.03333333333333333, 12], [1, 0.033333, False]]], [[[[[80.8, 0.0, 1.835], [0.0, 0.224, 0.0], [41.8, 0.0, 0.0]], 0.8, 0.1, 12], [1, 0.1, False]], [[[[246.0, 0.0, 0.0], [348.1, 1.08, 0.0]], 0.9, 0.1, 7], [2, 0.05, False]], [[[[0.0, 0.686, 0.27]], 0.9, 0.05, 11], [1, 0.05, False]], [[[[308.4, 0.84, 0.0], [0.0, 0.0, 0.663]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[120.7, 0.983, 1.303]], 0.9, 0.1, 9], [1, 0.1, False]], [[[[312.6, 0.0, 1.453]], 0.5, 0.1, 12], [3, 0.033333, False]], [[[[0.0, 0.0, 0.245]], 0.8, 0.1, 6], [1, 0.1, False]], [[[[41.7, 1.531, 1.755], [0.0, 0.0, 0.0], [0.0, 0.0, 1.05], [315.0, 0.0, 1.852]], 0.5, 0.05, 11], [2, 0.025, False]]], [[[[[255.8, 0.0, 0.123], [277.1, 0.0, 0.983], [226.4, 0.861, 0.0], [168.2, 0.0, 0.0]], 0.5, 0.016666666666666666, 12], [1, 0.016667, False]], [[[[112.1, 1.978, 0.662]], 0.9, 0.016666666666666666, 10], [1, 0.016667, False]], [[[[218.1, 0.0, 0.251], [45.1, 0.422, 1.694]], 0.8, 0.1, 7], [1, 0.1, False]], [[[[295.3, 1.026, 0.168], [142.6, 1.553, 1.729], [194.6, 0.909, 1.875], [131.0, 0.781, 0.0]], 0.8, 0.05, 11], [1, 0.05, False]], [[[[231.0, 0.0, 1.82], [25.6, 1.068, 0.0], [268.8, 1.477, 0.0]], 0.5, 0.05, 5], [2, 0.025, False]], [[[[320.1, 0.0, 1.766], [257.0, 0.0, 0.206]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[363.6, 0.0, 1.256], [386.5, 0.409, 0.0]], 0.5, 0.1, 9], [3, 0.033333, False]], [[[[0.0, 1.821, 0.0], [0.0, 0.0, 1.079], [397.4, 0.0, 1.153]], 0.8, 0.1, 4], [2, 0.05, False]]], [[[[[392.2, 0.719, 0.0], [179.3, 0.0, 0.0], [31.9, 0.866, 0.407]], 0.8, 0.03333333333333333, 7], [1, 0.033333, False]], [[[[0.0, 0.215, 0.0], [136.7, 0.0, 1.56]], 0.5, 0.05, 5], [1, 0.05, False]], [[[[70.2, 1.762, 1.247], [340.8, 0.0, 1.231]], 0.9, 0.1, 12], [2, 0.05, False]], [[[[0.0, 1.613, 0.0], [46.8, 1.168, 1.601], [255.1, 0.0, 0.0], [397.8, 0.0, 0.649]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[40.2, 0.248, 0.0], [60.6, 0.0, 1.539], [90.0, 0.532, 0.877], [0.0, 0.241, 1.358]], 0.5, 0.03333333333333333, 7], [1, 0.033333, False]], [[[[34.9, 1.893, 0.883], [94.6, 1.187, 0.0], [6.2, 0.0, 0.66]], 0.5, 0.1, 10], [2, 0.05, False]], [[[[0.0, 1.18, 0.594], [62.4, 0.0, 0.0], [347.8, 1.292, 0.0], [365.4, 0.457, 0.0]], 0.5, 0.016666666666666666, 3], [1, 0.016667, False]], [[[[235.0, 0.0, 1.683], [0.0, 1.505, 0.0]], 0.5, 0.1, 10], [2, 0.05, False]]]]
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.033333, False][1, 0.033333, False]Passed
case 1[1, 0.1, False][2, 0.05, False]Failed
case 2[1, 0.1, False][2, 0.05, False]Failed
case 3[1, 0.1, False][2, 0.05, False]Failed
case 4[2, 0.05, False][2, 0.05, False]Passed
case 5[1, 0.016667, False][1, 0.016667, False]Passed
case 6[1, 0.05, False][1, 0.05, False]Passed
case 7[1, 0.1, False][1, 0.1, True]Failed

SHA-256 / e7b05990f76287e9c656e19d5574659335928e26bf0266ee31b641c5bae45415

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(springs, safety, dt_frame, max_sub):
    hc = None
    for k, im1, im2 in springs:
        w = im1 + im2
        if im1 == 0 or k <= 0:
            continue
        h = safety * 2.0 / math.sqrt(k * w)
        if hc is None or h < hc:
            hc = h
    if hc is None:
        return [1, round(dt_frame, 6), False]
    n = math.ceil(dt_frame / hc - 1e-12)
    clamped = n > max_sub
    n = min(max(n, 1), max_sub)
    return [n, round(dt_frame / n, 6), clamped]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[[[39.9, 0.852, 0.112]], 0.9, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[167.6, 0.0, 1.769], [238.7, 0.0, 1.824], [101.3, 0.949, 0.0], [328.1, 0.0, 1.65]], 0.9, 0.1, 10], [2, 0.05, False]], [[[[51.4, 0.26, 0.0], [194.4, 1.517, 0.0]], 0.5, 0.1, 5], [2, 0.05, False]], [[[[172.3, 1.054, 0.0]], 0.5, 0.1, 12], [2, 0.05, False]], [[[[217.3, 0.0, 0.272], [226.4, 1.256, 1.506], [86.4, 1.936, 1.121]], 0.8, 0.1, 7], [2, 0.05, False]], [[[[106.5, 1.63, 0.0]], 0.9, 0.016666666666666666, 2], [1, 0.016667, False]], [[[[0.0, 0.185, 0.0], [369.7, 0.0, 1.891], [271.6, 0.712, 0.0], [0.0, 0.0, 0.0]], 0.9, 0.05, 5], [1, 0.05, False]], [[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]]], [[[[[239.0, 0.421, 0.398], [322.8, 0.0, 1.917], [189.8, 0.0, 0.0], [229.0, 1.496, 0.0]], 0.5, 0.05, 9], [2, 0.025, False]], [[[[329.8, 0.384, 1.012]], 0.5, 0.03333333333333333, 12], [1, 0.033333, False]], [[[[225.1, 0.0, 1.023]], 0.5, 0.016666666666666666, 4], [1, 0.016667, False]], [[[[254.1, 0.546, 0.0], [148.9, 0.748, 0.0], [149.5, 0.0, 0.0]], 0.5, 0.05, 1], [1, 0.05, False]], [[[[246.1, 0.0, 1.235]], 0.9, 0.1, 5], [1, 0.1, False]], [[[[68.2, 0.0, 1.77], [298.4, 1.344, 1.263]], 0.8, 0.05, 2], [1, 0.05, False]], [[[[397.6, 1.504, 0.0], [245.5, 1.813, 0.706], [252.0, 0.608, 0.796]], 0.5, 0.1, 9], [3, 0.033333, False]], [[[[356.6, 1.026, 0.0], [52.1, 1.266, 1.354]], 0.8, 0.03333333333333333, 12], [1, 0.033333, False]]], [[[[[80.8, 0.0, 1.835], [0.0, 0.224, 0.0], [41.8, 0.0, 0.0]], 0.8, 0.1, 12], [1, 0.1, False]], [[[[246.0, 0.0, 0.0], [348.1, 1.08, 0.0]], 0.9, 0.1, 7], [2, 0.05, False]], [[[[0.0, 0.686, 0.27]], 0.9, 0.05, 11], [1, 0.05, False]], [[[[308.4, 0.84, 0.0], [0.0, 0.0, 0.663]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[120.7, 0.983, 1.303]], 0.9, 0.1, 9], [1, 0.1, False]], [[[[312.6, 0.0, 1.453]], 0.5, 0.1, 12], [3, 0.033333, False]], [[[[0.0, 0.0, 0.245]], 0.8, 0.1, 6], [1, 0.1, False]], [[[[41.7, 1.531, 1.755], [0.0, 0.0, 0.0], [0.0, 0.0, 1.05], [315.0, 0.0, 1.852]], 0.5, 0.05, 11], [2, 0.025, False]]], [[[[[255.8, 0.0, 0.123], [277.1, 0.0, 0.983], [226.4, 0.861, 0.0], [168.2, 0.0, 0.0]], 0.5, 0.016666666666666666, 12], [1, 0.016667, False]], [[[[112.1, 1.978, 0.662]], 0.9, 0.016666666666666666, 10], [1, 0.016667, False]], [[[[218.1, 0.0, 0.251], [45.1, 0.422, 1.694]], 0.8, 0.1, 7], [1, 0.1, False]], [[[[295.3, 1.026, 0.168], [142.6, 1.553, 1.729], [194.6, 0.909, 1.875], [131.0, 0.781, 0.0]], 0.8, 0.05, 11], [1, 0.05, False]], [[[[231.0, 0.0, 1.82], [25.6, 1.068, 0.0], [268.8, 1.477, 0.0]], 0.5, 0.05, 5], [2, 0.025, False]], [[[[320.1, 0.0, 1.766], [257.0, 0.0, 0.206]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[363.6, 0.0, 1.256], [386.5, 0.409, 0.0]], 0.5, 0.1, 9], [3, 0.033333, False]], [[[[0.0, 1.821, 0.0], [0.0, 0.0, 1.079], [397.4, 0.0, 1.153]], 0.8, 0.1, 4], [2, 0.05, False]]], [[[[[392.2, 0.719, 0.0], [179.3, 0.0, 0.0], [31.9, 0.866, 0.407]], 0.8, 0.03333333333333333, 7], [1, 0.033333, False]], [[[[0.0, 0.215, 0.0], [136.7, 0.0, 1.56]], 0.5, 0.05, 5], [1, 0.05, False]], [[[[70.2, 1.762, 1.247], [340.8, 0.0, 1.231]], 0.9, 0.1, 12], [2, 0.05, False]], [[[[0.0, 1.613, 0.0], [46.8, 1.168, 1.601], [255.1, 0.0, 0.0], [397.8, 0.0, 0.649]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[40.2, 0.248, 0.0], [60.6, 0.0, 1.539], [90.0, 0.532, 0.877], [0.0, 0.241, 1.358]], 0.5, 0.03333333333333333, 7], [1, 0.033333, False]], [[[[34.9, 1.893, 0.883], [94.6, 1.187, 0.0], [6.2, 0.0, 0.66]], 0.5, 0.1, 10], [2, 0.05, False]], [[[[0.0, 1.18, 0.594], [62.4, 0.0, 0.0], [347.8, 1.292, 0.0], [365.4, 0.457, 0.0]], 0.5, 0.016666666666666666, 3], [1, 0.016667, False]], [[[[235.0, 0.0, 1.683], [0.0, 1.505, 0.0]], 0.5, 0.1, 10], [2, 0.05, False]]]]
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.033333, False][1, 0.033333, False]Passed
case 1[1, 0.1, False][2, 0.05, False]Failed
case 2[2, 0.05, False][2, 0.05, False]Passed
case 3[2, 0.05, False][2, 0.05, False]Passed
case 4[2, 0.05, False][2, 0.05, False]Passed
case 5[1, 0.016667, False][1, 0.016667, False]Passed
case 6[1, 0.05, False][1, 0.05, False]Passed
case 7[1, 0.1, True][1, 0.1, True]Passed

SHA-256 / 49a724ab413af88b03c5e543d15b29721d34a64870a64be1b5da929b58d9890a

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(springs, safety, dt_frame, max_sub):
    hc = None
    for k, im1, im2 in springs:
        w = im1 + im2
        if w == 0 or k <= 0:
            continue
        h = safety * 2.0 / math.sqrt(k * w)
        if hc is None or h < hc:
            hc = h
    if hc is None:
        return [1, round(dt_frame, 6), False]
    n = math.ceil(dt_frame / hc - 1e-12)
    clamped = n > max_sub
    n = min(max(n, 1), max_sub)
    return [n, round(dt_frame / n, 6), clamped]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[[[39.9, 0.852, 0.112]], 0.9, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[167.6, 0.0, 1.769], [238.7, 0.0, 1.824], [101.3, 0.949, 0.0], [328.1, 0.0, 1.65]], 0.9, 0.1, 10], [2, 0.05, False]], [[[[51.4, 0.26, 0.0], [194.4, 1.517, 0.0]], 0.5, 0.1, 5], [2, 0.05, False]], [[[[172.3, 1.054, 0.0]], 0.5, 0.1, 12], [2, 0.05, False]], [[[[217.3, 0.0, 0.272], [226.4, 1.256, 1.506], [86.4, 1.936, 1.121]], 0.8, 0.1, 7], [2, 0.05, False]], [[[[106.5, 1.63, 0.0]], 0.9, 0.016666666666666666, 2], [1, 0.016667, False]], [[[[0.0, 0.185, 0.0], [369.7, 0.0, 1.891], [271.6, 0.712, 0.0], [0.0, 0.0, 0.0]], 0.9, 0.05, 5], [1, 0.05, False]], [[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]]], [[[[[239.0, 0.421, 0.398], [322.8, 0.0, 1.917], [189.8, 0.0, 0.0], [229.0, 1.496, 0.0]], 0.5, 0.05, 9], [2, 0.025, False]], [[[[329.8, 0.384, 1.012]], 0.5, 0.03333333333333333, 12], [1, 0.033333, False]], [[[[225.1, 0.0, 1.023]], 0.5, 0.016666666666666666, 4], [1, 0.016667, False]], [[[[254.1, 0.546, 0.0], [148.9, 0.748, 0.0], [149.5, 0.0, 0.0]], 0.5, 0.05, 1], [1, 0.05, False]], [[[[246.1, 0.0, 1.235]], 0.9, 0.1, 5], [1, 0.1, False]], [[[[68.2, 0.0, 1.77], [298.4, 1.344, 1.263]], 0.8, 0.05, 2], [1, 0.05, False]], [[[[397.6, 1.504, 0.0], [245.5, 1.813, 0.706], [252.0, 0.608, 0.796]], 0.5, 0.1, 9], [3, 0.033333, False]], [[[[356.6, 1.026, 0.0], [52.1, 1.266, 1.354]], 0.8, 0.03333333333333333, 12], [1, 0.033333, False]]], [[[[[80.8, 0.0, 1.835], [0.0, 0.224, 0.0], [41.8, 0.0, 0.0]], 0.8, 0.1, 12], [1, 0.1, False]], [[[[246.0, 0.0, 0.0], [348.1, 1.08, 0.0]], 0.9, 0.1, 7], [2, 0.05, False]], [[[[0.0, 0.686, 0.27]], 0.9, 0.05, 11], [1, 0.05, False]], [[[[308.4, 0.84, 0.0], [0.0, 0.0, 0.663]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[120.7, 0.983, 1.303]], 0.9, 0.1, 9], [1, 0.1, False]], [[[[312.6, 0.0, 1.453]], 0.5, 0.1, 12], [3, 0.033333, False]], [[[[0.0, 0.0, 0.245]], 0.8, 0.1, 6], [1, 0.1, False]], [[[[41.7, 1.531, 1.755], [0.0, 0.0, 0.0], [0.0, 0.0, 1.05], [315.0, 0.0, 1.852]], 0.5, 0.05, 11], [2, 0.025, False]]], [[[[[255.8, 0.0, 0.123], [277.1, 0.0, 0.983], [226.4, 0.861, 0.0], [168.2, 0.0, 0.0]], 0.5, 0.016666666666666666, 12], [1, 0.016667, False]], [[[[112.1, 1.978, 0.662]], 0.9, 0.016666666666666666, 10], [1, 0.016667, False]], [[[[218.1, 0.0, 0.251], [45.1, 0.422, 1.694]], 0.8, 0.1, 7], [1, 0.1, False]], [[[[295.3, 1.026, 0.168], [142.6, 1.553, 1.729], [194.6, 0.909, 1.875], [131.0, 0.781, 0.0]], 0.8, 0.05, 11], [1, 0.05, False]], [[[[231.0, 0.0, 1.82], [25.6, 1.068, 0.0], [268.8, 1.477, 0.0]], 0.5, 0.05, 5], [2, 0.025, False]], [[[[320.1, 0.0, 1.766], [257.0, 0.0, 0.206]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[363.6, 0.0, 1.256], [386.5, 0.409, 0.0]], 0.5, 0.1, 9], [3, 0.033333, False]], [[[[0.0, 1.821, 0.0], [0.0, 0.0, 1.079], [397.4, 0.0, 1.153]], 0.8, 0.1, 4], [2, 0.05, False]]], [[[[[392.2, 0.719, 0.0], [179.3, 0.0, 0.0], [31.9, 0.866, 0.407]], 0.8, 0.03333333333333333, 7], [1, 0.033333, False]], [[[[0.0, 0.215, 0.0], [136.7, 0.0, 1.56]], 0.5, 0.05, 5], [1, 0.05, False]], [[[[70.2, 1.762, 1.247], [340.8, 0.0, 1.231]], 0.9, 0.1, 12], [2, 0.05, False]], [[[[0.0, 1.613, 0.0], [46.8, 1.168, 1.601], [255.1, 0.0, 0.0], [397.8, 0.0, 0.649]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[40.2, 0.248, 0.0], [60.6, 0.0, 1.539], [90.0, 0.532, 0.877], [0.0, 0.241, 1.358]], 0.5, 0.03333333333333333, 7], [1, 0.033333, False]], [[[[34.9, 1.893, 0.883], [94.6, 1.187, 0.0], [6.2, 0.0, 0.66]], 0.5, 0.1, 10], [2, 0.05, False]], [[[[0.0, 1.18, 0.594], [62.4, 0.0, 0.0], [347.8, 1.292, 0.0], [365.4, 0.457, 0.0]], 0.5, 0.016666666666666666, 3], [1, 0.016667, False]], [[[[235.0, 0.0, 1.683], [0.0, 1.505, 0.0]], 0.5, 0.1, 10], [2, 0.05, False]]]]
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.033333, False][1, 0.033333, False]Passed
case 1[2, 0.05, False][2, 0.05, False]Passed
case 2[2, 0.05, False][2, 0.05, False]Passed
case 3[2, 0.05, False][2, 0.05, False]Passed
case 4[2, 0.05, False][2, 0.05, False]Passed
case 5[1, 0.016667, False][1, 0.016667, False]Passed
case 6[1, 0.05, False][1, 0.05, False]Passed
case 7[1, 0.1, True][1, 0.1, True]Passed

SHA-256 / 119ecc142a775faef9a5c844636244cf8f03b7988505ef51922687458dbf3c77

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

Case digest / 2dfa7aac80a77b73f7a539d9364f926556d92cde5a0531bad9e519fbcb5af0a1