FAILURE MAP
← Case archive

FA-80746 / Bidirectional text layout / Open access

Direction-relative alignment: center rounding by direction · case 01

Odd slack in RTL lines is split toward the wrong side.

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

ROOT CAUSE

Centering always rounds the offset down.

VERIFIED REPAIR

Round up for RTL so the extra unit goes toward the start side.

Unsuccessful approach: Always rounding up breaks LTR lines.

Case contract

Input [line width, content width, align, rtl]. justify (last line) behaves as start. start = left for LTR, right for RTL; end is the opposite. Overflowing centered content aligns to start. center offset is floor(slack/2) for LTR and ceil(slack/2) for RTL. Return the x offset of the content.

Why this case matters

Mixed right-to-left and left-to-right text must resolve levels and visual order exactly, or words, numbers and carets land in the wrong place.

1 / The failure

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

N = 1
observations = []
def solve(x):
    lw, cw, align, rtl = x
    if align == 'justify':
        align = 'start'
    if align == 'start':
        align = 'right' if rtl else 'left'
    elif align == 'end':
        align = 'left' if rtl else 'right'
    if cw > lw and align == 'center':
        align = 'left' if not rtl else 'right'
    if align == 'left':
        return 0
    if align == 'right':
        return lw - cw
    slack = lw - cw
    return slack // 2
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('odd slack RTL center', [20, 5, 'center', True], 8), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [22, 5, 'center', False], 8), ('partial-repair probe', [39, 34, 'center', False], 2), ('RTL end is left', [20, 5, 'end', True], 0), ('overflowing center RTL', [10, 15, 'center', True], -5), ('control layout', [23, 19, 'left', True], 0), ('control layout', [28, 30, 'left', False], 0)], [('regression: center rounding by direction', [33, 4, 'center', True], 15), ('regression: center rounding by direction', [23, 12, 'center', True], 6), ('partial-repair probe', [39, 34, 'center', False], 2), ('partial-repair probe', [37, 14, 'center', False], 11), ('RTL end is left', [20, 5, 'end', True], 0), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [23, 29, 'left', True], 0), ('control layout', [31, 48, 'right', False], -17)], [('regression: center rounding by direction', [40, 5, 'center', True], 18), ('regression: center rounding by direction', [33, 4, 'center', True], 15), ('partial-repair probe', [29, 18, 'center', False], 5), ('partial-repair probe', [22, 5, 'center', False], 8), ('overflowing center RTL', [10, 15, 'center', True], -5), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [33, 1, 'justify', False], 0), ('control layout', [25, 40, 'left', False], 0)], [('odd slack RTL center', [20, 5, 'center', True], 8), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [19, 4, 'center', False], 7), ('partial-repair probe', [22, 5, 'center', False], 8), ('overflowing center RTL', [10, 15, 'center', True], -5), ('RTL end is left', [20, 5, 'end', True], 0), ('control layout', [26, 12, 'right', False], 14), ('control layout', [20, 47, 'right', True], -27)], [('regression: center rounding by direction', [17, 8, 'center', True], 5), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [39, 34, 'center', False], 2), ('partial-repair probe', [29, 18, 'center', False], 5), ('overflowing center RTL', [10, 15, 'center', True], -5), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [33, 4, 'left', False], 0), ('control layout', [17, 4, 'end', True], 0)]]
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
odd slack RTL center78Failed
regression: center rounding by direction12Failed
partial-repair probe88Passed
partial-repair probe22Passed
RTL end is left00Passed
overflowing center RTL-5-5Passed
control layout00Passed
control layout00Passed

SHA-256 / 4429b6c409896abcae462e0f1cbd1d96bbc477eb6d0a253bdc094d14e45e8e07

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    lw, cw, align, rtl = x
    if align == 'justify':
        align = 'start'
    if align == 'start':
        align = 'right' if rtl else 'left'
    elif align == 'end':
        align = 'left' if rtl else 'right'
    if cw > lw and align == 'center':
        align = 'left' if not rtl else 'right'
    if align == 'left':
        return 0
    if align == 'right':
        return lw - cw
    slack = lw - cw
    return (slack + 1) // 2
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('odd slack RTL center', [20, 5, 'center', True], 8), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [22, 5, 'center', False], 8), ('partial-repair probe', [39, 34, 'center', False], 2), ('RTL end is left', [20, 5, 'end', True], 0), ('overflowing center RTL', [10, 15, 'center', True], -5), ('control layout', [23, 19, 'left', True], 0), ('control layout', [28, 30, 'left', False], 0)], [('regression: center rounding by direction', [33, 4, 'center', True], 15), ('regression: center rounding by direction', [23, 12, 'center', True], 6), ('partial-repair probe', [39, 34, 'center', False], 2), ('partial-repair probe', [37, 14, 'center', False], 11), ('RTL end is left', [20, 5, 'end', True], 0), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [23, 29, 'left', True], 0), ('control layout', [31, 48, 'right', False], -17)], [('regression: center rounding by direction', [40, 5, 'center', True], 18), ('regression: center rounding by direction', [33, 4, 'center', True], 15), ('partial-repair probe', [29, 18, 'center', False], 5), ('partial-repair probe', [22, 5, 'center', False], 8), ('overflowing center RTL', [10, 15, 'center', True], -5), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [33, 1, 'justify', False], 0), ('control layout', [25, 40, 'left', False], 0)], [('odd slack RTL center', [20, 5, 'center', True], 8), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [19, 4, 'center', False], 7), ('partial-repair probe', [22, 5, 'center', False], 8), ('overflowing center RTL', [10, 15, 'center', True], -5), ('RTL end is left', [20, 5, 'end', True], 0), ('control layout', [26, 12, 'right', False], 14), ('control layout', [20, 47, 'right', True], -27)], [('regression: center rounding by direction', [17, 8, 'center', True], 5), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [39, 34, 'center', False], 2), ('partial-repair probe', [29, 18, 'center', False], 5), ('overflowing center RTL', [10, 15, 'center', True], -5), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [33, 4, 'left', False], 0), ('control layout', [17, 4, 'end', True], 0)]]
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
odd slack RTL center88Passed
regression: center rounding by direction22Passed
partial-repair probe98Failed
partial-repair probe32Failed
RTL end is left00Passed
overflowing center RTL-5-5Passed
control layout00Passed
control layout00Passed

SHA-256 / af84e0904d28d897f2a5ed1b5cebc11fc8a6b01c7a1fa56dc945a476f598e675

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    lw, cw, align, rtl = x
    if align == 'justify':
        align = 'start'
    if align == 'start':
        align = 'right' if rtl else 'left'
    elif align == 'end':
        align = 'left' if rtl else 'right'
    if cw > lw and align == 'center':
        align = 'left' if not rtl else 'right'
    if align == 'left':
        return 0
    if align == 'right':
        return lw - cw
    slack = lw - cw
    return (slack + 1) // 2 if rtl else slack // 2
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('odd slack RTL center', [20, 5, 'center', True], 8), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [22, 5, 'center', False], 8), ('partial-repair probe', [39, 34, 'center', False], 2), ('RTL end is left', [20, 5, 'end', True], 0), ('overflowing center RTL', [10, 15, 'center', True], -5), ('control layout', [23, 19, 'left', True], 0), ('control layout', [28, 30, 'left', False], 0)], [('regression: center rounding by direction', [33, 4, 'center', True], 15), ('regression: center rounding by direction', [23, 12, 'center', True], 6), ('partial-repair probe', [39, 34, 'center', False], 2), ('partial-repair probe', [37, 14, 'center', False], 11), ('RTL end is left', [20, 5, 'end', True], 0), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [23, 29, 'left', True], 0), ('control layout', [31, 48, 'right', False], -17)], [('regression: center rounding by direction', [40, 5, 'center', True], 18), ('regression: center rounding by direction', [33, 4, 'center', True], 15), ('partial-repair probe', [29, 18, 'center', False], 5), ('partial-repair probe', [22, 5, 'center', False], 8), ('overflowing center RTL', [10, 15, 'center', True], -5), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [33, 1, 'justify', False], 0), ('control layout', [25, 40, 'left', False], 0)], [('odd slack RTL center', [20, 5, 'center', True], 8), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [19, 4, 'center', False], 7), ('partial-repair probe', [22, 5, 'center', False], 8), ('overflowing center RTL', [10, 15, 'center', True], -5), ('RTL end is left', [20, 5, 'end', True], 0), ('control layout', [26, 12, 'right', False], 14), ('control layout', [20, 47, 'right', True], -27)], [('regression: center rounding by direction', [17, 8, 'center', True], 5), ('regression: center rounding by direction', [35, 32, 'center', True], 2), ('partial-repair probe', [39, 34, 'center', False], 2), ('partial-repair probe', [29, 18, 'center', False], 5), ('overflowing center RTL', [10, 15, 'center', True], -5), ('odd slack RTL center', [20, 5, 'center', True], 8), ('control layout', [33, 4, 'left', False], 0), ('control layout', [17, 4, 'end', True], 0)]]
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
odd slack RTL center88Passed
regression: center rounding by direction22Passed
partial-repair probe88Passed
partial-repair probe22Passed
RTL end is left00Passed
overflowing center RTL-5-5Passed
control layout00Passed
control layout00Passed

SHA-256 / 8ee9ed4dca665d94dca337e8930af221d1aca986cf2f8df5786f8e1124a1aef7

Verification & scope

A deterministic toy bidi model over stipulated class labels and integer levels; it is inspired by, but does not claim conformance to, any published algorithm. 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:56.741460+00:00.

Case digest / 0255393f51dddb6b0602eb77a35b15f4ccd9cb34908860ce5db849fa7960f679