FAILURE MAP
← Case archive

FA-86956 / Physics integrator stability / Open access

Substep count floors the frame to critical step ratio · case 01

Frames needing 2.3 critical steps run only two, each above the stability limit.

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

ROOT CAUSE

The count uses int() instead of ceil.

VERIFIED REPAIR

Round up so every substep is below the critical step.

Unsuccessful approach: round() still rounds 2.3 down.

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 = int(dt_frame / hc)
    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 = [[[[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[314.1, 1.117, 0.362], [352.9, 1.216, 0.0], [82.7, 1.696, 1.393], [286.0, 0.0, 0.0]], 0.8, 0.1, 3], [2, 0.05, False]], [[[[63.5, 0.263, 1.804]], 0.5, 0.1, 7], [2, 0.05, False]], [[[[247.5, 1.904, 0.114], [32.3, 0.0, 0.0]], 0.8, 0.1, 4], [2, 0.05, False]], [[[[148.8, 1.386, 0.442]], 0.8, 0.016666666666666666, 4], [1, 0.016667, False]], [[[[243.1, 0.776, 1.14], [246.7, 0.0, 0.273], [228.3, 0.0, 0.108]], 0.5, 0.05, 6], [2, 0.025, False]], [[[[71.3, 0.479, 1.246], [255.9, 0.0, 0.0], [268.0, 1.892, 0.0], [0.0, 0.578, 1.103]], 0.8, 0.05, 2], [1, 0.05, False]], [[[[1.7, 0.0, 0.738], [238.1, 1.296, 1.984], [33.2, 1.635, 0.592], [264.5, 0.289, 1.887]], 0.8, 0.016666666666666666, 12], [1, 0.016667, False]]], [[[[[211.7, 1.064, 0.812], [384.0, 1.693, 1.686]], 0.5, 0.1, 7], [4, 0.025, False]], [[[[356.6, 1.026, 0.0], [52.1, 1.266, 1.354]], 0.8, 0.03333333333333333, 12], [1, 0.033333, False]], [[[[288.5, 0.0, 0.0], [135.2, 0.764, 0.24], [389.1, 0.45, 1.145]], 0.5, 0.05, 6], [2, 0.025, False]], [[[[294.3, 0.0, 0.849], [289.0, 0.318, 0.614], [302.6, 0.0, 1.053], [355.1, 1.372, 1.454]], 0.9, 0.05, 9], [1, 0.05, False]], [[[[35.3, 1.054, 0.0], [371.9, 1.288, 1.017]], 0.8, 0.1, 5], [2, 0.05, False]], [[[[97.9, 0.409, 0.102], [187.1, 0.0, 1.876], [175.5, 0.772, 1.165]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[208.5, 1.926, 0.106]], 0.9, 0.1, 11], [2, 0.05, False]], [[[[355.4, 1.01, 0.67], [366.8, 1.311, 0.796], [352.6, 1.297, 0.0]], 0.9, 0.016666666666666666, 4], [1, 0.016667, False]]], [[[[[195.8, 1.522, 0.0], [333.9, 0.0, 1.879], [318.2, 1.159, 0.237]], 0.8, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[0.0, 0.476, 0.0], [18.2, 1.282, 0.988], [243.3, 0.297, 1.244]], 0.5, 0.016666666666666666, 10], [1, 0.016667, False]], [[[[36.1, 0.0, 0.765], [178.8, 1.201, 1.609], [361.7, 0.0, 0.24]], 0.8, 0.1, 12], [2, 0.05, False]], [[[[219.4, 0.47, 0.357], [244.7, 1.869, 0.0], [206.2, 0.734, 1.4]], 0.9, 0.1, 8], [2, 0.05, False]], [[[[98.4, 0.155, 0.544], [243.2, 0.0, 0.932], [174.9, 0.0, 0.913], [326.4, 1.87, 1.935]], 0.8, 0.05, 8], [2, 0.025, 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]], [[[[308.1, 0.0, 0.454], [396.0, 0.194, 1.062]], 0.5, 0.05, 10], [2, 0.025, False]], [[[[0.0, 0.686, 0.27]], 0.9, 0.05, 11], [1, 0.05, False]]], [[[[[0.0, 1.568, 0.0]], 0.9, 0.1, 2], [1, 0.1, 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]], [[[[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]], [[[[369.2, 0.945, 0.32], [145.4, 0.0, 0.0]], 0.5, 0.05, 11], [2, 0.025, False]], [[[[168.2, 1.818, 0.0], [238.5, 1.719, 0.0]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[383.4, 0.0, 1.175], [0.0, 1.603, 0.212], [368.6, 0.0, 0.0]], 0.8, 0.05, 10], [1, 0.05, False]], [[[[320.1, 0.0, 1.766], [257.0, 0.0, 0.206]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[111.2, 0.217, 0.0], [0.0, 0.0, 0.0]], 0.5, 0.016666666666666666, 8], [1, 0.016667, 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]], [[[[373.0, 1.043, 1.229], [318.1, 0.0, 1.235], [6.5, 0.12, 0.231]], 0.5, 0.05, 5], [2, 0.025, False]], [[[[180.7, 1.578, 1.59], [1.3, 0.945, 0.0], [192.2, 0.0, 1.838]], 0.9, 0.1, 10], [2, 0.05, False]], [[[[189.2, 0.0, 0.264], [120.6, 0.373, 0.98], [395.1, 0.0, 1.272]], 0.8, 0.05, 9], [1, 0.05, False]], [[[[396.9, 0.251, 0.0], [341.7, 1.253, 0.745]], 0.5, 0.05, 9], [2, 0.025, False]], [[[[317.5, 0.0, 0.172]], 0.9, 0.03333333333333333, 3], [1, 0.033333, False]], [[[[149.6, 1.802, 0.77], [102.2, 1.828, 0.436]], 0.5, 0.1, 7], [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]]]]
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][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[1, 0.016667, False][1, 0.016667, False]Passed
case 5[1, 0.05, False][2, 0.025, False]Failed
case 6[1, 0.05, False][1, 0.05, False]Passed
case 7[1, 0.016667, False][1, 0.016667, False]Passed

SHA-256 / b2c7c8dbedc40cf28627143ddb65b8807ef43b813f39bbe7522f0ef4f792e6f0

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 = round(dt_frame / hc)
    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 = [[[[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[314.1, 1.117, 0.362], [352.9, 1.216, 0.0], [82.7, 1.696, 1.393], [286.0, 0.0, 0.0]], 0.8, 0.1, 3], [2, 0.05, False]], [[[[63.5, 0.263, 1.804]], 0.5, 0.1, 7], [2, 0.05, False]], [[[[247.5, 1.904, 0.114], [32.3, 0.0, 0.0]], 0.8, 0.1, 4], [2, 0.05, False]], [[[[148.8, 1.386, 0.442]], 0.8, 0.016666666666666666, 4], [1, 0.016667, False]], [[[[243.1, 0.776, 1.14], [246.7, 0.0, 0.273], [228.3, 0.0, 0.108]], 0.5, 0.05, 6], [2, 0.025, False]], [[[[71.3, 0.479, 1.246], [255.9, 0.0, 0.0], [268.0, 1.892, 0.0], [0.0, 0.578, 1.103]], 0.8, 0.05, 2], [1, 0.05, False]], [[[[1.7, 0.0, 0.738], [238.1, 1.296, 1.984], [33.2, 1.635, 0.592], [264.5, 0.289, 1.887]], 0.8, 0.016666666666666666, 12], [1, 0.016667, False]]], [[[[[211.7, 1.064, 0.812], [384.0, 1.693, 1.686]], 0.5, 0.1, 7], [4, 0.025, False]], [[[[356.6, 1.026, 0.0], [52.1, 1.266, 1.354]], 0.8, 0.03333333333333333, 12], [1, 0.033333, False]], [[[[288.5, 0.0, 0.0], [135.2, 0.764, 0.24], [389.1, 0.45, 1.145]], 0.5, 0.05, 6], [2, 0.025, False]], [[[[294.3, 0.0, 0.849], [289.0, 0.318, 0.614], [302.6, 0.0, 1.053], [355.1, 1.372, 1.454]], 0.9, 0.05, 9], [1, 0.05, False]], [[[[35.3, 1.054, 0.0], [371.9, 1.288, 1.017]], 0.8, 0.1, 5], [2, 0.05, False]], [[[[97.9, 0.409, 0.102], [187.1, 0.0, 1.876], [175.5, 0.772, 1.165]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[208.5, 1.926, 0.106]], 0.9, 0.1, 11], [2, 0.05, False]], [[[[355.4, 1.01, 0.67], [366.8, 1.311, 0.796], [352.6, 1.297, 0.0]], 0.9, 0.016666666666666666, 4], [1, 0.016667, False]]], [[[[[195.8, 1.522, 0.0], [333.9, 0.0, 1.879], [318.2, 1.159, 0.237]], 0.8, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[0.0, 0.476, 0.0], [18.2, 1.282, 0.988], [243.3, 0.297, 1.244]], 0.5, 0.016666666666666666, 10], [1, 0.016667, False]], [[[[36.1, 0.0, 0.765], [178.8, 1.201, 1.609], [361.7, 0.0, 0.24]], 0.8, 0.1, 12], [2, 0.05, False]], [[[[219.4, 0.47, 0.357], [244.7, 1.869, 0.0], [206.2, 0.734, 1.4]], 0.9, 0.1, 8], [2, 0.05, False]], [[[[98.4, 0.155, 0.544], [243.2, 0.0, 0.932], [174.9, 0.0, 0.913], [326.4, 1.87, 1.935]], 0.8, 0.05, 8], [2, 0.025, 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]], [[[[308.1, 0.0, 0.454], [396.0, 0.194, 1.062]], 0.5, 0.05, 10], [2, 0.025, False]], [[[[0.0, 0.686, 0.27]], 0.9, 0.05, 11], [1, 0.05, False]]], [[[[[0.0, 1.568, 0.0]], 0.9, 0.1, 2], [1, 0.1, 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]], [[[[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]], [[[[369.2, 0.945, 0.32], [145.4, 0.0, 0.0]], 0.5, 0.05, 11], [2, 0.025, False]], [[[[168.2, 1.818, 0.0], [238.5, 1.719, 0.0]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[383.4, 0.0, 1.175], [0.0, 1.603, 0.212], [368.6, 0.0, 0.0]], 0.8, 0.05, 10], [1, 0.05, False]], [[[[320.1, 0.0, 1.766], [257.0, 0.0, 0.206]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[111.2, 0.217, 0.0], [0.0, 0.0, 0.0]], 0.5, 0.016666666666666666, 8], [1, 0.016667, 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]], [[[[373.0, 1.043, 1.229], [318.1, 0.0, 1.235], [6.5, 0.12, 0.231]], 0.5, 0.05, 5], [2, 0.025, False]], [[[[180.7, 1.578, 1.59], [1.3, 0.945, 0.0], [192.2, 0.0, 1.838]], 0.9, 0.1, 10], [2, 0.05, False]], [[[[189.2, 0.0, 0.264], [120.6, 0.373, 0.98], [395.1, 0.0, 1.272]], 0.8, 0.05, 9], [1, 0.05, False]], [[[[396.9, 0.251, 0.0], [341.7, 1.253, 0.745]], 0.5, 0.05, 9], [2, 0.025, False]], [[[[317.5, 0.0, 0.172]], 0.9, 0.03333333333333333, 3], [1, 0.033333, False]], [[[[149.6, 1.802, 0.77], [102.2, 1.828, 0.436]], 0.5, 0.1, 7], [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]]]]
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][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[1, 0.016667, False][1, 0.016667, False]Passed
case 5[1, 0.05, False][2, 0.025, False]Failed
case 6[1, 0.05, False][1, 0.05, False]Passed
case 7[1, 0.016667, False][1, 0.016667, False]Passed

SHA-256 / 7205d029d31eafc69456c249b642bbfe4dc2adab0087e1edd0ef6b5cbce498e5

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 = [[[[[[62.5, 0.699, 0.764], [271.2, 0.743, 0.0]], 0.5, 0.1, 1], [1, 0.1, True]], [[[[314.1, 1.117, 0.362], [352.9, 1.216, 0.0], [82.7, 1.696, 1.393], [286.0, 0.0, 0.0]], 0.8, 0.1, 3], [2, 0.05, False]], [[[[63.5, 0.263, 1.804]], 0.5, 0.1, 7], [2, 0.05, False]], [[[[247.5, 1.904, 0.114], [32.3, 0.0, 0.0]], 0.8, 0.1, 4], [2, 0.05, False]], [[[[148.8, 1.386, 0.442]], 0.8, 0.016666666666666666, 4], [1, 0.016667, False]], [[[[243.1, 0.776, 1.14], [246.7, 0.0, 0.273], [228.3, 0.0, 0.108]], 0.5, 0.05, 6], [2, 0.025, False]], [[[[71.3, 0.479, 1.246], [255.9, 0.0, 0.0], [268.0, 1.892, 0.0], [0.0, 0.578, 1.103]], 0.8, 0.05, 2], [1, 0.05, False]], [[[[1.7, 0.0, 0.738], [238.1, 1.296, 1.984], [33.2, 1.635, 0.592], [264.5, 0.289, 1.887]], 0.8, 0.016666666666666666, 12], [1, 0.016667, False]]], [[[[[211.7, 1.064, 0.812], [384.0, 1.693, 1.686]], 0.5, 0.1, 7], [4, 0.025, False]], [[[[356.6, 1.026, 0.0], [52.1, 1.266, 1.354]], 0.8, 0.03333333333333333, 12], [1, 0.033333, False]], [[[[288.5, 0.0, 0.0], [135.2, 0.764, 0.24], [389.1, 0.45, 1.145]], 0.5, 0.05, 6], [2, 0.025, False]], [[[[294.3, 0.0, 0.849], [289.0, 0.318, 0.614], [302.6, 0.0, 1.053], [355.1, 1.372, 1.454]], 0.9, 0.05, 9], [1, 0.05, False]], [[[[35.3, 1.054, 0.0], [371.9, 1.288, 1.017]], 0.8, 0.1, 5], [2, 0.05, False]], [[[[97.9, 0.409, 0.102], [187.1, 0.0, 1.876], [175.5, 0.772, 1.165]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[208.5, 1.926, 0.106]], 0.9, 0.1, 11], [2, 0.05, False]], [[[[355.4, 1.01, 0.67], [366.8, 1.311, 0.796], [352.6, 1.297, 0.0]], 0.9, 0.016666666666666666, 4], [1, 0.016667, False]]], [[[[[195.8, 1.522, 0.0], [333.9, 0.0, 1.879], [318.2, 1.159, 0.237]], 0.8, 0.03333333333333333, 8], [1, 0.033333, False]], [[[[0.0, 0.476, 0.0], [18.2, 1.282, 0.988], [243.3, 0.297, 1.244]], 0.5, 0.016666666666666666, 10], [1, 0.016667, False]], [[[[36.1, 0.0, 0.765], [178.8, 1.201, 1.609], [361.7, 0.0, 0.24]], 0.8, 0.1, 12], [2, 0.05, False]], [[[[219.4, 0.47, 0.357], [244.7, 1.869, 0.0], [206.2, 0.734, 1.4]], 0.9, 0.1, 8], [2, 0.05, False]], [[[[98.4, 0.155, 0.544], [243.2, 0.0, 0.932], [174.9, 0.0, 0.913], [326.4, 1.87, 1.935]], 0.8, 0.05, 8], [2, 0.025, 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]], [[[[308.1, 0.0, 0.454], [396.0, 0.194, 1.062]], 0.5, 0.05, 10], [2, 0.025, False]], [[[[0.0, 0.686, 0.27]], 0.9, 0.05, 11], [1, 0.05, False]]], [[[[[0.0, 1.568, 0.0]], 0.9, 0.1, 2], [1, 0.1, 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]], [[[[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]], [[[[369.2, 0.945, 0.32], [145.4, 0.0, 0.0]], 0.5, 0.05, 11], [2, 0.025, False]], [[[[168.2, 1.818, 0.0], [238.5, 1.719, 0.0]], 0.8, 0.1, 1], [1, 0.1, True]], [[[[383.4, 0.0, 1.175], [0.0, 1.603, 0.212], [368.6, 0.0, 0.0]], 0.8, 0.05, 10], [1, 0.05, False]], [[[[320.1, 0.0, 1.766], [257.0, 0.0, 0.206]], 0.9, 0.1, 3], [2, 0.05, False]], [[[[111.2, 0.217, 0.0], [0.0, 0.0, 0.0]], 0.5, 0.016666666666666666, 8], [1, 0.016667, 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]], [[[[373.0, 1.043, 1.229], [318.1, 0.0, 1.235], [6.5, 0.12, 0.231]], 0.5, 0.05, 5], [2, 0.025, False]], [[[[180.7, 1.578, 1.59], [1.3, 0.945, 0.0], [192.2, 0.0, 1.838]], 0.9, 0.1, 10], [2, 0.05, False]], [[[[189.2, 0.0, 0.264], [120.6, 0.373, 0.98], [395.1, 0.0, 1.272]], 0.8, 0.05, 9], [1, 0.05, False]], [[[[396.9, 0.251, 0.0], [341.7, 1.253, 0.745]], 0.5, 0.05, 9], [2, 0.025, False]], [[[[317.5, 0.0, 0.172]], 0.9, 0.03333333333333333, 3], [1, 0.033333, False]], [[[[149.6, 1.802, 0.77], [102.2, 1.828, 0.436]], 0.5, 0.1, 7], [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]]]]
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[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[1, 0.016667, False][1, 0.016667, False]Passed
case 5[2, 0.025, False][2, 0.025, False]Passed
case 6[1, 0.05, False][1, 0.05, False]Passed
case 7[1, 0.016667, False][1, 0.016667, False]Passed

SHA-256 / 3ee6060f45645ce360a385823386f030bdd31ed7af08771a2422d0b7e49b1fc1

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

Case digest / bf53138c38c9cc49e6941869c509cedbb6d0e51e72b689c31fe431375840753d