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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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