FAILURE MAP
← Case archive

FA-86961 / Physics integrator stability / Open access

Substep clamp flag is evaluated after clamping · case 01

The caller is never told the frame ran above the stability limit.

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

ROOT CAUSE

clamped is computed from n after it was already limited to max_sub.

VERIFIED REPAIR

Compare the required count against max_sub before clamping.

Unsuccessful approach: Using >= flags frames that fit exactly into max_sub.

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 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)
    n = min(max(n, 1), max_sub)
    clamped = n > 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 = [[[[[[378.8, 0.569, 1.348], [61.1, 0.579, 0.0], [0.2, 1.46, 0.0], [0.0, 0.938, 0.0]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[316.7, 0.733, 0.0]], 0.8, 0.1, 10], [1, 0.1, False]], [[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[145.8, 1.307, 1.565], [334.4, 0.0, 0.0], [29.2, 1.907, 0.28], [373.4, 0.0, 1.993]], 0.9, 0.05, 4], [1, 0.05, False]], [[[[15.8, 0.273, 1.659], [277.3, 0.0, 1.254]], 0.8, 0.05, 6], [1, 0.05, False]], [[[[0.0, 0.133, 0.288], [277.5, 1.393, 0.313], [186.0, 1.259, 0.245], [78.0, 1.034, 0.148]], 0.9, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[176.6, 0.318, 1.727]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[42.7, 0.482, 1.06]], 0.9, 0.05, 1], [1, 0.05, False]]], [[[[[260.7, 0.0, 0.335], [150.6, 0.0, 0.816]], 0.9, 0.03333333333333333, 11], [1, 0.033333, False]], [[[[67.9, 1.44, 0.0]], 0.5, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[107.0, 1.953, 1.474]], 0.8, 0.03333333333333333, 5], [1, 0.033333, False]], [[[[241.3, 1.334, 1.412]], 0.9, 0.05, 9], [1, 0.05, False]], [[[[277.3, 0.0, 0.0], [30.7, 0.0, 1.872], [165.7, 0.129, 0.529], [238.8, 0.875, 1.263]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[176.2, 1.797, 1.825], [298.2, 0.526, 0.891], [118.3, 0.227, 0.0]], 0.5, 0.1, 2], [2, 0.05, True]], [[[[283.2, 0.406, 0.385], [273.1, 1.173, 0.813], [273.0, 0.0, 0.0], [213.0, 0.422, 0.0]], 0.9, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[211.7, 1.064, 0.812], [384.0, 1.693, 1.686]], 0.5, 0.1, 7], [4, 0.025, False]]], [[[[[383.5, 1.166, 0.0], [100.1, 0.961, 1.146], [46.9, 0.497, 1.006]], 0.9, 0.016666666666666666, 7], [1, 0.016667, False]], [[[[0.0, 0.898, 0.365], [308.5, 1.46, 0.0], [162.4, 0.916, 1.761], [343.1, 1.271, 0.525]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[379.0, 1.672, 0.307], [79.8, 1.44, 0.884], [166.0, 1.451, 0.616], [7.7, 1.811, 1.263]], 0.9, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[84.5, 1.172, 0.389], [387.6, 0.894, 0.997], [341.4, 0.801, 0.0]], 0.9, 0.05, 1], [1, 0.05, False]], [[[[223.0, 0.142, 0.0]], 0.5, 0.05, 1], [1, 0.05, False]], [[[[0.0, 1.705, 0.0], [65.7, 1.843, 0.0], [101.1, 0.412, 0.0]], 0.9, 0.016666666666666666, 2], [1, 0.016667, False]], [[[[308.4, 0.84, 0.0], [0.0, 0.0, 0.663]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[83.1, 0.423, 0.998], [213.6, 1.913, 0.0], [0.0, 1.148, 0.983], [369.5, 0.121, 0.638]], 0.9, 0.1, 1], [1, 0.1, True]]], [[[[[168.9, 0.395, 0.0]], 0.5, 0.016666666666666666, 12], [1, 0.016667, False]], [[[[168.2, 1.818, 0.0], [238.5, 1.719, 0.0]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[385.7, 1.862, 1.408], [57.2, 1.136, 0.0], [93.5, 0.0, 0.0]], 0.5, 0.05, 12], [2, 0.025, False]], [[[[168.4, 1.477, 0.765], [16.7, 1.246, 0.543], [148.6, 0.0, 1.059]], 0.8, 0.016666666666666666, 1], [1, 0.016667, False]], [[[[225.4, 0.134, 1.333]], 0.9, 0.1, 6], [2, 0.05, False]], [[[[0.0, 0.0, 0.55]], 0.5, 0.03333333333333333, 3], [1, 0.033333, False]], [[[[30.6, 0.0, 0.0], [0.0, 0.809, 1.964], [251.0, 0.701, 0.449]], 0.9, 0.05, 1], [1, 0.05, False]], [[[[196.8, 0.748, 0.21], [399.3, 0.0, 0.0], [91.0, 1.67, 0.0], [235.5, 1.69, 1.824]], 0.8, 0.1, 4], [2, 0.05, False]]], [[[[[53.5, 0.682, 0.825], [18.2, 0.0, 1.819], [392.4, 0.545, 0.0]], 0.8, 0.05, 5], [1, 0.05, False]], [[[[0.0, 0.0, 0.936], [147.1, 0.218, 1.736], [215.3, 0.319, 0.0], [306.3, 0.41, 0.131]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[61.6, 1.778, 0.85], [183.6, 0.628, 1.833], [57.3, 1.103, 1.169]], 0.5, 0.016666666666666666, 7], [1, 0.016667, False]], [[[[9.5, 0.999, 0.0], [322.6, 1.353, 0.773], [183.6, 1.731, 0.412]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[218.3, 0.713, 1.347], [385.3, 0.486, 1.456], [167.3, 0.489, 0.0], [21.6, 1.706, 1.068]], 0.5, 0.016666666666666666, 6], [1, 0.016667, False]], [[[[89.9, 1.633, 0.0], [0.0, 1.203, 0.742]], 0.5, 0.016666666666666666, 5], [1, 0.016667, 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]], [[[[68.0, 1.125, 0.993], [0.0, 1.059, 0.332]], 0.5, 0.016666666666666666, 1], [1, 0.016667, 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.1, False][1, 0.1, True]Failed
case 1[1, 0.1, False][1, 0.1, False]Passed
case 2[1, 0.1, False][1, 0.1, True]Failed
case 3[1, 0.05, False][1, 0.05, False]Passed
case 4[1, 0.05, False][1, 0.05, False]Passed
case 5[1, 0.033333, False][1, 0.033333, False]Passed
case 6[1, 0.1, False][1, 0.1, True]Failed
case 7[1, 0.05, False][1, 0.05, False]Passed

SHA-256 / 3ebf2e58ad55d7c10e3567925215112ec4bf9b34040aa0079e25791e4c43aaa4

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 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 = [[[[[[378.8, 0.569, 1.348], [61.1, 0.579, 0.0], [0.2, 1.46, 0.0], [0.0, 0.938, 0.0]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[316.7, 0.733, 0.0]], 0.8, 0.1, 10], [1, 0.1, False]], [[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[145.8, 1.307, 1.565], [334.4, 0.0, 0.0], [29.2, 1.907, 0.28], [373.4, 0.0, 1.993]], 0.9, 0.05, 4], [1, 0.05, False]], [[[[15.8, 0.273, 1.659], [277.3, 0.0, 1.254]], 0.8, 0.05, 6], [1, 0.05, False]], [[[[0.0, 0.133, 0.288], [277.5, 1.393, 0.313], [186.0, 1.259, 0.245], [78.0, 1.034, 0.148]], 0.9, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[176.6, 0.318, 1.727]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[42.7, 0.482, 1.06]], 0.9, 0.05, 1], [1, 0.05, False]]], [[[[[260.7, 0.0, 0.335], [150.6, 0.0, 0.816]], 0.9, 0.03333333333333333, 11], [1, 0.033333, False]], [[[[67.9, 1.44, 0.0]], 0.5, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[107.0, 1.953, 1.474]], 0.8, 0.03333333333333333, 5], [1, 0.033333, False]], [[[[241.3, 1.334, 1.412]], 0.9, 0.05, 9], [1, 0.05, False]], [[[[277.3, 0.0, 0.0], [30.7, 0.0, 1.872], [165.7, 0.129, 0.529], [238.8, 0.875, 1.263]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[176.2, 1.797, 1.825], [298.2, 0.526, 0.891], [118.3, 0.227, 0.0]], 0.5, 0.1, 2], [2, 0.05, True]], [[[[283.2, 0.406, 0.385], [273.1, 1.173, 0.813], [273.0, 0.0, 0.0], [213.0, 0.422, 0.0]], 0.9, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[211.7, 1.064, 0.812], [384.0, 1.693, 1.686]], 0.5, 0.1, 7], [4, 0.025, False]]], [[[[[383.5, 1.166, 0.0], [100.1, 0.961, 1.146], [46.9, 0.497, 1.006]], 0.9, 0.016666666666666666, 7], [1, 0.016667, False]], [[[[0.0, 0.898, 0.365], [308.5, 1.46, 0.0], [162.4, 0.916, 1.761], [343.1, 1.271, 0.525]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[379.0, 1.672, 0.307], [79.8, 1.44, 0.884], [166.0, 1.451, 0.616], [7.7, 1.811, 1.263]], 0.9, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[84.5, 1.172, 0.389], [387.6, 0.894, 0.997], [341.4, 0.801, 0.0]], 0.9, 0.05, 1], [1, 0.05, False]], [[[[223.0, 0.142, 0.0]], 0.5, 0.05, 1], [1, 0.05, False]], [[[[0.0, 1.705, 0.0], [65.7, 1.843, 0.0], [101.1, 0.412, 0.0]], 0.9, 0.016666666666666666, 2], [1, 0.016667, False]], [[[[308.4, 0.84, 0.0], [0.0, 0.0, 0.663]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[83.1, 0.423, 0.998], [213.6, 1.913, 0.0], [0.0, 1.148, 0.983], [369.5, 0.121, 0.638]], 0.9, 0.1, 1], [1, 0.1, True]]], [[[[[168.9, 0.395, 0.0]], 0.5, 0.016666666666666666, 12], [1, 0.016667, False]], [[[[168.2, 1.818, 0.0], [238.5, 1.719, 0.0]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[385.7, 1.862, 1.408], [57.2, 1.136, 0.0], [93.5, 0.0, 0.0]], 0.5, 0.05, 12], [2, 0.025, False]], [[[[168.4, 1.477, 0.765], [16.7, 1.246, 0.543], [148.6, 0.0, 1.059]], 0.8, 0.016666666666666666, 1], [1, 0.016667, False]], [[[[225.4, 0.134, 1.333]], 0.9, 0.1, 6], [2, 0.05, False]], [[[[0.0, 0.0, 0.55]], 0.5, 0.03333333333333333, 3], [1, 0.033333, False]], [[[[30.6, 0.0, 0.0], [0.0, 0.809, 1.964], [251.0, 0.701, 0.449]], 0.9, 0.05, 1], [1, 0.05, False]], [[[[196.8, 0.748, 0.21], [399.3, 0.0, 0.0], [91.0, 1.67, 0.0], [235.5, 1.69, 1.824]], 0.8, 0.1, 4], [2, 0.05, False]]], [[[[[53.5, 0.682, 0.825], [18.2, 0.0, 1.819], [392.4, 0.545, 0.0]], 0.8, 0.05, 5], [1, 0.05, False]], [[[[0.0, 0.0, 0.936], [147.1, 0.218, 1.736], [215.3, 0.319, 0.0], [306.3, 0.41, 0.131]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[61.6, 1.778, 0.85], [183.6, 0.628, 1.833], [57.3, 1.103, 1.169]], 0.5, 0.016666666666666666, 7], [1, 0.016667, False]], [[[[9.5, 0.999, 0.0], [322.6, 1.353, 0.773], [183.6, 1.731, 0.412]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[218.3, 0.713, 1.347], [385.3, 0.486, 1.456], [167.3, 0.489, 0.0], [21.6, 1.706, 1.068]], 0.5, 0.016666666666666666, 6], [1, 0.016667, False]], [[[[89.9, 1.633, 0.0], [0.0, 1.203, 0.742]], 0.5, 0.016666666666666666, 5], [1, 0.016667, 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]], [[[[68.0, 1.125, 0.993], [0.0, 1.059, 0.332]], 0.5, 0.016666666666666666, 1], [1, 0.016667, 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.1, True][1, 0.1, True]Passed
case 1[1, 0.1, False][1, 0.1, False]Passed
case 2[1, 0.1, True][1, 0.1, True]Passed
case 3[1, 0.05, False][1, 0.05, False]Passed
case 4[1, 0.05, False][1, 0.05, False]Passed
case 5[1, 0.033333, True][1, 0.033333, False]Failed
case 6[1, 0.1, True][1, 0.1, True]Passed
case 7[1, 0.05, True][1, 0.05, False]Failed

SHA-256 / c2c27a8679f65f3cd79a6796c03f87f58e47d8afdc8dfd694717f2632ce28552

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 = [[[[[[378.8, 0.569, 1.348], [61.1, 0.579, 0.0], [0.2, 1.46, 0.0], [0.0, 0.938, 0.0]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[316.7, 0.733, 0.0]], 0.8, 0.1, 10], [1, 0.1, False]], [[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[145.8, 1.307, 1.565], [334.4, 0.0, 0.0], [29.2, 1.907, 0.28], [373.4, 0.0, 1.993]], 0.9, 0.05, 4], [1, 0.05, False]], [[[[15.8, 0.273, 1.659], [277.3, 0.0, 1.254]], 0.8, 0.05, 6], [1, 0.05, False]], [[[[0.0, 0.133, 0.288], [277.5, 1.393, 0.313], [186.0, 1.259, 0.245], [78.0, 1.034, 0.148]], 0.9, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[176.6, 0.318, 1.727]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[42.7, 0.482, 1.06]], 0.9, 0.05, 1], [1, 0.05, False]]], [[[[[260.7, 0.0, 0.335], [150.6, 0.0, 0.816]], 0.9, 0.03333333333333333, 11], [1, 0.033333, False]], [[[[67.9, 1.44, 0.0]], 0.5, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[107.0, 1.953, 1.474]], 0.8, 0.03333333333333333, 5], [1, 0.033333, False]], [[[[241.3, 1.334, 1.412]], 0.9, 0.05, 9], [1, 0.05, False]], [[[[277.3, 0.0, 0.0], [30.7, 0.0, 1.872], [165.7, 0.129, 0.529], [238.8, 0.875, 1.263]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[176.2, 1.797, 1.825], [298.2, 0.526, 0.891], [118.3, 0.227, 0.0]], 0.5, 0.1, 2], [2, 0.05, True]], [[[[283.2, 0.406, 0.385], [273.1, 1.173, 0.813], [273.0, 0.0, 0.0], [213.0, 0.422, 0.0]], 0.9, 0.03333333333333333, 1], [1, 0.033333, False]], [[[[211.7, 1.064, 0.812], [384.0, 1.693, 1.686]], 0.5, 0.1, 7], [4, 0.025, False]]], [[[[[383.5, 1.166, 0.0], [100.1, 0.961, 1.146], [46.9, 0.497, 1.006]], 0.9, 0.016666666666666666, 7], [1, 0.016667, False]], [[[[0.0, 0.898, 0.365], [308.5, 1.46, 0.0], [162.4, 0.916, 1.761], [343.1, 1.271, 0.525]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[379.0, 1.672, 0.307], [79.8, 1.44, 0.884], [166.0, 1.451, 0.616], [7.7, 1.811, 1.263]], 0.9, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[84.5, 1.172, 0.389], [387.6, 0.894, 0.997], [341.4, 0.801, 0.0]], 0.9, 0.05, 1], [1, 0.05, False]], [[[[223.0, 0.142, 0.0]], 0.5, 0.05, 1], [1, 0.05, False]], [[[[0.0, 1.705, 0.0], [65.7, 1.843, 0.0], [101.1, 0.412, 0.0]], 0.9, 0.016666666666666666, 2], [1, 0.016667, False]], [[[[308.4, 0.84, 0.0], [0.0, 0.0, 0.663]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[83.1, 0.423, 0.998], [213.6, 1.913, 0.0], [0.0, 1.148, 0.983], [369.5, 0.121, 0.638]], 0.9, 0.1, 1], [1, 0.1, True]]], [[[[[168.9, 0.395, 0.0]], 0.5, 0.016666666666666666, 12], [1, 0.016667, False]], [[[[168.2, 1.818, 0.0], [238.5, 1.719, 0.0]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[385.7, 1.862, 1.408], [57.2, 1.136, 0.0], [93.5, 0.0, 0.0]], 0.5, 0.05, 12], [2, 0.025, False]], [[[[168.4, 1.477, 0.765], [16.7, 1.246, 0.543], [148.6, 0.0, 1.059]], 0.8, 0.016666666666666666, 1], [1, 0.016667, False]], [[[[225.4, 0.134, 1.333]], 0.9, 0.1, 6], [2, 0.05, False]], [[[[0.0, 0.0, 0.55]], 0.5, 0.03333333333333333, 3], [1, 0.033333, False]], [[[[30.6, 0.0, 0.0], [0.0, 0.809, 1.964], [251.0, 0.701, 0.449]], 0.9, 0.05, 1], [1, 0.05, False]], [[[[196.8, 0.748, 0.21], [399.3, 0.0, 0.0], [91.0, 1.67, 0.0], [235.5, 1.69, 1.824]], 0.8, 0.1, 4], [2, 0.05, False]]], [[[[[53.5, 0.682, 0.825], [18.2, 0.0, 1.819], [392.4, 0.545, 0.0]], 0.8, 0.05, 5], [1, 0.05, False]], [[[[0.0, 0.0, 0.936], [147.1, 0.218, 1.736], [215.3, 0.319, 0.0], [306.3, 0.41, 0.131]], 0.5, 0.1, 2], [2, 0.05, False]], [[[[61.6, 1.778, 0.85], [183.6, 0.628, 1.833], [57.3, 1.103, 1.169]], 0.5, 0.016666666666666666, 7], [1, 0.016667, False]], [[[[9.5, 0.999, 0.0], [322.6, 1.353, 0.773], [183.6, 1.731, 0.412]], 0.9, 0.1, 1], [1, 0.1, True]], [[[[218.3, 0.713, 1.347], [385.3, 0.486, 1.456], [167.3, 0.489, 0.0], [21.6, 1.706, 1.068]], 0.5, 0.016666666666666666, 6], [1, 0.016667, False]], [[[[89.9, 1.633, 0.0], [0.0, 1.203, 0.742]], 0.5, 0.016666666666666666, 5], [1, 0.016667, 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]], [[[[68.0, 1.125, 0.993], [0.0, 1.059, 0.332]], 0.5, 0.016666666666666666, 1], [1, 0.016667, 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.1, True][1, 0.1, True]Passed
case 1[1, 0.1, False][1, 0.1, False]Passed
case 2[1, 0.1, True][1, 0.1, True]Passed
case 3[1, 0.05, False][1, 0.05, False]Passed
case 4[1, 0.05, False][1, 0.05, False]Passed
case 5[1, 0.033333, False][1, 0.033333, False]Passed
case 6[1, 0.1, True][1, 0.1, True]Passed
case 7[1, 0.05, False][1, 0.05, False]Passed

SHA-256 / 67908fea0ad12b67a6827700ea6124ad9746ddccc1c79e8095358ca94f2d30f2

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

Case digest / 687d358c89bc950c2f585ee2367f77366215c1f0304243fbf40503af8c6c231e