FAILURE MAP
← Case archive

FA-78281 / Subtitle cue timing / Open access

Recommended cue duration on frame grid: frame back-conversion · case 01

The reported millisecond duration is shorter than the frame count covers.

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

ROOT CAUSE

Frames are converted back to ms with floor instead of ceiling.

VERIFIED REPAIR

Convert frames back with a ceiling.

Unsuccessful approach: Dividing by the rate numerator before multiplying by the denominator loses precision.

Case contract

rules [base,per_char,min,max]. Count characters other than spaces and line breaks; duration = base + count*per_char, clamped to [min,max]; then rounded up to whole frames at fps [N,D]. Return [frames, ceil(frames*1000*D/N)].

Why this case matters

Subtitle timing defects shift, hide or overlap captions that viewers depend on for comprehension and accessibility.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(text, fps, rules):
    base,per,lo,hi=rules
    n=len(text.replace(' ','').replace('\n',''))
    d=base+n*per
    d=max(lo,min(hi,d))
    num,den=fps
    fr=-((-d*num)//(1000*den))
    return [fr,fr*1000*den//num]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: frame back-conversion', ['Two\nlines here', [30000, 1001], [0, 70, 800, 7000]], [26, 868]), ('regression variant: frame back-conversion', ['Two\nlines here', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair probe: frame back-conversion', ['A much longer subtitle line to read', [24000, 1001], [300, 50, 1200, 1500]], [36, 1502]), ('partial repair variant: frame back-conversion', ['', [24, 1], [300, 50, 1200, 1500]], [29, 1209]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['   ', [25, 1], [700, 100, 1000, 2000]], [25, 1000]), ('normal control', ['Two\nlines here', [25, 1], [300, 50, 1200, 1500]], [30, 1200]), ('normal control', ['Hi', [24, 1], [500, 60, 1000, 6000]], [24, 1000])], [('regression: frame back-conversion', ['ok', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('regression variant: frame back-conversion', ['Two\nlines here', [24000, 1001], [700, 100, 1000, 2000]], [46, 1919]), ('partial repair probe: frame back-conversion', ['Hi', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('partial repair variant: frame back-conversion', ['Hi', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Hello world', [25, 1], [300, 50, 1200, 1500]], [30, 1200]), ('normal control', ['A much longer subtitle line to read', [30, 1], [700, 100, 1000, 2000]], [60, 2000]), ('normal control', ['Hello world', [30, 1], [300, 50, 1200, 1500]], [36, 1200])], [('regression: frame back-conversion', ['A much longer subtitle line to read', [24000, 1001], [300, 50, 1200, 1500]], [36, 1502]), ('regression variant: frame back-conversion', ['Hello world', [30000, 1001], [0, 70, 800, 7000]], [24, 801]), ('partial repair probe: frame back-conversion', ['   ', [24000, 1001], [0, 70, 800, 7000]], [20, 835]), ('partial repair variant: frame back-conversion', ['', [30000, 1001], [0, 70, 800, 7000]], [24, 801]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['   ', [24, 1], [700, 100, 1000, 2000]], [24, 1000]), ('normal control', ['ok', [24, 1], [700, 100, 1000, 2000]], [24, 1000]), ('normal control', ['', [24000, 1001], [700, 100, 1000, 2000]], [24, 1001])], [('regression: frame back-conversion', ['Hi', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('regression variant: frame back-conversion', ['ok', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair probe: frame back-conversion', ['Two\nlines here', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair variant: frame back-conversion', ['Hello world', [30000, 1001], [500, 60, 1000, 6000]], [33, 1102]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Hi', [30000, 1001], [700, 100, 1000, 2000]], [30, 1001]), ('normal control', ['Two\nlines here', [24, 1], [0, 70, 800, 7000]], [21, 875]), ('normal control', ['ok', [25, 1], [500, 60, 1000, 6000]], [25, 1000])], [('regression: frame back-conversion', ['   ', [24000, 1001], [0, 70, 800, 7000]], [20, 835]), ('regression variant: frame back-conversion', ['', [24, 1], [300, 50, 1200, 1500]], [29, 1209]), ('partial repair probe: frame back-conversion', ['Two\nlines here', [24000, 1001], [700, 100, 1000, 2000]], [46, 1919]), ('partial repair variant: frame back-conversion', ['A much longer subtitle line to read', [24, 1], [0, 70, 800, 7000]], [49, 2042]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Two\nlines here', [25, 1], [0, 70, 800, 7000]], [21, 840]), ('normal control', ['Hello world', [30, 1], [500, 60, 1000, 6000]], [33, 1100])]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
regression: frame back-conversion[26, 867][26, 868]Failed
regression variant: frame back-conversion[36, 1201][36, 1202]Failed
partial repair probe: frame back-conversion[36, 1501][36, 1502]Failed
partial repair variant: frame back-conversion[29, 1208][29, 1209]Failed
boundary control[25, 1000][25, 1000]Passed
boundary control[50, 2000][50, 2000]Passed
normal control[25, 1000][25, 1000]Passed
normal control[30, 1200][30, 1200]Passed
normal control[24, 1000][24, 1000]Passed

SHA-256 / eba0b48f6b1afd92cbea55df43dc98a2d705d48b5a97fa5309519697bd5e5ce0

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(text, fps, rules):
    base,per,lo,hi=rules
    n=len(text.replace(' ','').replace('\n',''))
    d=base+n*per
    d=max(lo,min(hi,d))
    num,den=fps
    fr=-((-d*num)//(1000*den))
    return [fr,fr*1000//num*den]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: frame back-conversion', ['Two\nlines here', [30000, 1001], [0, 70, 800, 7000]], [26, 868]), ('regression variant: frame back-conversion', ['Two\nlines here', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair probe: frame back-conversion', ['A much longer subtitle line to read', [24000, 1001], [300, 50, 1200, 1500]], [36, 1502]), ('partial repair variant: frame back-conversion', ['', [24, 1], [300, 50, 1200, 1500]], [29, 1209]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['   ', [25, 1], [700, 100, 1000, 2000]], [25, 1000]), ('normal control', ['Two\nlines here', [25, 1], [300, 50, 1200, 1500]], [30, 1200]), ('normal control', ['Hi', [24, 1], [500, 60, 1000, 6000]], [24, 1000])], [('regression: frame back-conversion', ['ok', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('regression variant: frame back-conversion', ['Two\nlines here', [24000, 1001], [700, 100, 1000, 2000]], [46, 1919]), ('partial repair probe: frame back-conversion', ['Hi', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('partial repair variant: frame back-conversion', ['Hi', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Hello world', [25, 1], [300, 50, 1200, 1500]], [30, 1200]), ('normal control', ['A much longer subtitle line to read', [30, 1], [700, 100, 1000, 2000]], [60, 2000]), ('normal control', ['Hello world', [30, 1], [300, 50, 1200, 1500]], [36, 1200])], [('regression: frame back-conversion', ['A much longer subtitle line to read', [24000, 1001], [300, 50, 1200, 1500]], [36, 1502]), ('regression variant: frame back-conversion', ['Hello world', [30000, 1001], [0, 70, 800, 7000]], [24, 801]), ('partial repair probe: frame back-conversion', ['   ', [24000, 1001], [0, 70, 800, 7000]], [20, 835]), ('partial repair variant: frame back-conversion', ['', [30000, 1001], [0, 70, 800, 7000]], [24, 801]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['   ', [24, 1], [700, 100, 1000, 2000]], [24, 1000]), ('normal control', ['ok', [24, 1], [700, 100, 1000, 2000]], [24, 1000]), ('normal control', ['', [24000, 1001], [700, 100, 1000, 2000]], [24, 1001])], [('regression: frame back-conversion', ['Hi', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('regression variant: frame back-conversion', ['ok', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair probe: frame back-conversion', ['Two\nlines here', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair variant: frame back-conversion', ['Hello world', [30000, 1001], [500, 60, 1000, 6000]], [33, 1102]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Hi', [30000, 1001], [700, 100, 1000, 2000]], [30, 1001]), ('normal control', ['Two\nlines here', [24, 1], [0, 70, 800, 7000]], [21, 875]), ('normal control', ['ok', [25, 1], [500, 60, 1000, 6000]], [25, 1000])], [('regression: frame back-conversion', ['   ', [24000, 1001], [0, 70, 800, 7000]], [20, 835]), ('regression variant: frame back-conversion', ['', [24, 1], [300, 50, 1200, 1500]], [29, 1209]), ('partial repair probe: frame back-conversion', ['Two\nlines here', [24000, 1001], [700, 100, 1000, 2000]], [46, 1919]), ('partial repair variant: frame back-conversion', ['A much longer subtitle line to read', [24, 1], [0, 70, 800, 7000]], [49, 2042]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Two\nlines here', [25, 1], [0, 70, 800, 7000]], [21, 840]), ('normal control', ['Hello world', [30, 1], [500, 60, 1000, 6000]], [33, 1100])]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
regression: frame back-conversion[26, 0][26, 868]Failed
regression variant: frame back-conversion[36, 1001][36, 1202]Failed
partial repair probe: frame back-conversion[36, 1001][36, 1502]Failed
partial repair variant: frame back-conversion[29, 1208][29, 1209]Failed
boundary control[25, 1000][25, 1000]Passed
boundary control[50, 2000][50, 2000]Passed
normal control[25, 1000][25, 1000]Passed
normal control[30, 1200][30, 1200]Passed
normal control[24, 1000][24, 1000]Passed

SHA-256 / 355cdfda236e1acd1e548e02f5693de6edf02815b46bcdd43153bf8c715db49e

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(text, fps, rules):
    base,per,lo,hi=rules
    n=len(text.replace(' ','').replace('\n',''))
    d=base+n*per
    d=max(lo,min(hi,d))
    num,den=fps
    fr=-((-d*num)//(1000*den))
    return [fr,-((-fr*1000*den)//num)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: frame back-conversion', ['Two\nlines here', [30000, 1001], [0, 70, 800, 7000]], [26, 868]), ('regression variant: frame back-conversion', ['Two\nlines here', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair probe: frame back-conversion', ['A much longer subtitle line to read', [24000, 1001], [300, 50, 1200, 1500]], [36, 1502]), ('partial repair variant: frame back-conversion', ['', [24, 1], [300, 50, 1200, 1500]], [29, 1209]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['   ', [25, 1], [700, 100, 1000, 2000]], [25, 1000]), ('normal control', ['Two\nlines here', [25, 1], [300, 50, 1200, 1500]], [30, 1200]), ('normal control', ['Hi', [24, 1], [500, 60, 1000, 6000]], [24, 1000])], [('regression: frame back-conversion', ['ok', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('regression variant: frame back-conversion', ['Two\nlines here', [24000, 1001], [700, 100, 1000, 2000]], [46, 1919]), ('partial repair probe: frame back-conversion', ['Hi', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('partial repair variant: frame back-conversion', ['Hi', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Hello world', [25, 1], [300, 50, 1200, 1500]], [30, 1200]), ('normal control', ['A much longer subtitle line to read', [30, 1], [700, 100, 1000, 2000]], [60, 2000]), ('normal control', ['Hello world', [30, 1], [300, 50, 1200, 1500]], [36, 1200])], [('regression: frame back-conversion', ['A much longer subtitle line to read', [24000, 1001], [300, 50, 1200, 1500]], [36, 1502]), ('regression variant: frame back-conversion', ['Hello world', [30000, 1001], [0, 70, 800, 7000]], [24, 801]), ('partial repair probe: frame back-conversion', ['   ', [24000, 1001], [0, 70, 800, 7000]], [20, 835]), ('partial repair variant: frame back-conversion', ['', [30000, 1001], [0, 70, 800, 7000]], [24, 801]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['   ', [24, 1], [700, 100, 1000, 2000]], [24, 1000]), ('normal control', ['ok', [24, 1], [700, 100, 1000, 2000]], [24, 1000]), ('normal control', ['', [24000, 1001], [700, 100, 1000, 2000]], [24, 1001])], [('regression: frame back-conversion', ['Hi', [24, 1], [0, 70, 800, 7000]], [20, 834]), ('regression variant: frame back-conversion', ['ok', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair probe: frame back-conversion', ['Two\nlines here', [30000, 1001], [300, 50, 1200, 1500]], [36, 1202]), ('partial repair variant: frame back-conversion', ['Hello world', [30000, 1001], [500, 60, 1000, 6000]], [33, 1102]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Hi', [30000, 1001], [700, 100, 1000, 2000]], [30, 1001]), ('normal control', ['Two\nlines here', [24, 1], [0, 70, 800, 7000]], [21, 875]), ('normal control', ['ok', [25, 1], [500, 60, 1000, 6000]], [25, 1000])], [('regression: frame back-conversion', ['   ', [24000, 1001], [0, 70, 800, 7000]], [20, 835]), ('regression variant: frame back-conversion', ['', [24, 1], [300, 50, 1200, 1500]], [29, 1209]), ('partial repair probe: frame back-conversion', ['Two\nlines here', [24000, 1001], [700, 100, 1000, 2000]], [46, 1919]), ('partial repair variant: frame back-conversion', ['A much longer subtitle line to read', [24, 1], [0, 70, 800, 7000]], [49, 2042]), ('boundary control', ['Hi', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('boundary control', ['A much longer subtitle line to read', [25, 1], [500, 60, 1000, 2000]], [50, 2000]), ('normal control', ['', [25, 1], [500, 60, 1000, 6000]], [25, 1000]), ('normal control', ['Two\nlines here', [25, 1], [0, 70, 800, 7000]], [21, 840]), ('normal control', ['Hello world', [30, 1], [500, 60, 1000, 6000]], [33, 1100])]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
regression: frame back-conversion[26, 868][26, 868]Passed
regression variant: frame back-conversion[36, 1202][36, 1202]Passed
partial repair probe: frame back-conversion[36, 1502][36, 1502]Passed
partial repair variant: frame back-conversion[29, 1209][29, 1209]Passed
boundary control[25, 1000][25, 1000]Passed
boundary control[50, 2000][50, 2000]Passed
normal control[25, 1000][25, 1000]Passed
normal control[30, 1200][30, 1200]Passed
normal control[24, 1000][24, 1000]Passed

SHA-256 / 1dc67084c1f39a934ab99357a5009849570a1932454df7dde8bc32d6a47d5c38

Verification & scope

A deterministic bounded teaching model with a stipulated toy contract; it does not claim conformance to any subtitle standard. 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:49:33.982287+00:00.

Case digest / 76e98e883246d4e76b371dc2c144ff6993a30655c566298b6b12a759c32aa1ad