FAILURE MAP
← Case archive

FA-80391 / Bidirectional text layout / Open access

Weak type resolution: common separator number types · case 01

A comma between a European and an Arabic number is absorbed.

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

ROOT CAUSE

The W4 CS rule does not require both neighbours to have the same number type.

VERIFIED REPAIR

Require both neighbours to be the same number type.

Unsuccessful approach: Handling only European numbers drops the Arabic-number case.

Case contract

Input [classes of one isolating run sequence, sos]. W1 NSM takes the previous type (sos at start; ON after LRI/RLI/FSI/PDI). W2 EN after the last strong AL becomes AN. W3 AL->R. W4 a single ES between EN and EN -> EN; a single CS between two numbers of the same type takes that type. W5 a run of ET adjacent to EN -> EN. W6 remaining ES/ET/CS -> ON. W7 EN whose last strong (sos at start) is L -> L. Return resolved classes.

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):
    types, sos = x
    t = list(types)
    n = len(t)
    prev = sos
    for i in range(n):
        if t[i] == 'NSM':
            t[i] = 'ON' if prev in ('LRI', 'RLI', 'FSI', 'PDI') else prev
        prev = t[i]
    last_strong = sos
    for i in range(n):
        if t[i] in ('L', 'R', 'AL'):
            last_strong = t[i]
        elif t[i] == 'EN' and last_strong == 'AL':
            t[i] = 'AN'
    t = ['R' if c == 'AL' else c for c in t]
    for i in range(1, n - 1):
        if t[i] == 'ES' and t[i - 1] == 'EN' and t[i + 1] == 'EN':
            t[i] = 'EN'
        elif t[i] == 'CS' and t[i - 1] in ('EN', 'AN') and t[i + 1] in ('EN', 'AN'):
            t[i] = t[i - 1]
    i = 0
    while i < n:
        if t[i] == 'ET':
            j = i
            while j < n and t[j] == 'ET':
                j += 1
            if (i > 0 and t[i - 1] == 'EN') or (j < n and t[j] == 'EN'):
                for k in range(i, j):
                    t[k] = 'EN'
            i = j
        else:
            i += 1
    t = ['ON' if c in ('ES', 'ET', 'CS') else c for c in t]
    strong = sos
    for i in range(n):
        if t[i] in ('L', 'R'):
            strong = t[i]
        elif t[i] == 'EN' and strong == 'L':
            t[i] = 'L'
    return t
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: common separator number types', [['AL', 'EN', 'NSM', 'R', 'NSM', 'EN', 'CS', 'AN', 'EN'], 'L'], ['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('NSM chain after R', [['R', 'NSM', 'NSM', 'EN'], 'L'], ['R', 'R', 'R', 'EN']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['L', 'AN', 'L', 'PDI', 'ET', 'AL', 'NSM'], 'L'], ['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']), ('control layout', [['AL', 'WS', 'AL', 'L'], 'L'], ['R', 'WS', 'R', 'L'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'ON', 'LRI'], 'L'], ['ON', 'L', 'ON', 'AN', 'ON', 'LRI']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['L', 'AL', 'PDI', 'LRI', 'AL', 'EN', 'CS', 'EN', 'WS', 'CS'], 'R'], ['L', 'R', 'PDI', 'LRI', 'R', 'AN', 'AN', 'AN', 'WS', 'ON']), ('NSM chain after R', [['R', 'NSM', 'NSM', 'EN'], 'L'], ['R', 'R', 'R', 'EN']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('control layout', [['AL', 'ET', 'L', 'L', 'ET', 'CS', 'NSM', 'ET', 'ON', 'L'], 'R'], ['R', 'ON', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L']), ('control layout', [['L', 'L', 'AN', 'EN'], 'L'], ['L', 'L', 'AN', 'L'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'L', 'ON', 'AL'], 'L'], ['ON', 'L', 'ON', 'AN', 'L', 'ON', 'R']), ('regression: common separator number types', [['AL', 'EN', 'NSM', 'R', 'NSM', 'EN', 'CS', 'AN', 'EN'], 'L'], ['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']), ('partial-repair probe', [['EN', 'EN', 'AL', 'EN', 'CS', 'EN', 'PDI', 'ON', 'AN'], 'R'], ['EN', 'EN', 'R', 'AN', 'AN', 'AN', 'PDI', 'ON', 'AN']), ('partial-repair probe', [['L', 'AL', 'PDI', 'LRI', 'AL', 'EN', 'CS', 'EN', 'WS', 'CS'], 'R'], ['L', 'R', 'PDI', 'LRI', 'R', 'AN', 'AN', 'AN', 'WS', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['R', 'PDI', 'L', 'L', 'NSM', 'AL', 'EN', 'AN'], 'R'], ['R', 'PDI', 'L', 'L', 'L', 'R', 'AN', 'AN']), ('control layout', [['AL', 'NSM', 'CS', 'CS', 'AL'], 'L'], ['R', 'R', 'ON', 'ON', 'R'])], [('regression: common separator number types', [['AN', 'AN', 'CS', 'EN', 'ON', 'EN', 'EN'], 'R'], ['AN', 'AN', 'ON', 'EN', 'ON', 'EN', 'EN']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('plus between numbers', [['EN', 'ES', 'EN', 'R'], 'R'], ['EN', 'EN', 'EN', 'R']), ('control layout', [['ES', 'EN', 'ET', 'ON', 'WS', 'ET', 'AL', 'NSM', 'PDI', 'AL'], 'L'], ['ON', 'L', 'L', 'ON', 'WS', 'ON', 'R', 'R', 'PDI', 'R']), ('control layout', [['ET', 'AL', 'ES', 'NSM', 'ES', 'EN', 'PDI', 'WS', 'AN', 'CS'], 'R'], ['ON', 'R', 'ON', 'ON', 'ON', 'AN', 'PDI', 'WS', 'AN', 'ON'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'L', 'ON', 'AL'], 'L'], ['ON', 'L', 'ON', 'AN', 'L', 'ON', 'R']), ('regression: common separator number types', [['R', 'L', 'AN', 'CS', 'EN', 'CS', 'NSM', 'ES', 'AL'], 'L'], ['R', 'L', 'AN', 'ON', 'L', 'ON', 'ON', 'ON', 'R']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['L', 'LRI', 'EN'], 'L'], ['L', 'LRI', 'L']), ('control layout', [['AL', 'PDI', 'ES', 'EN', 'L', 'R', 'WS', 'ES', 'CS', 'CS'], 'L'], ['R', 'PDI', 'ON', 'AN', 'L', 'R', 'WS', 'ON', 'ON', 'ON'])]]
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: common separator number types['R', 'AN', 'AN', 'R', 'R', 'EN', 'EN', 'AN', 'EN']['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']Failed
regression: common separator number types['L', 'ON', 'AN', 'AN', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']Failed
partial-repair probe['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']Passed
Arabic number context['R', 'AN', 'AN', 'AN']['R', 'AN', 'AN', 'AN']Passed
NSM chain after R['R', 'R', 'R', 'EN']['R', 'R', 'R', 'EN']Passed
NSM after isolate initiator['LRI', 'ON', 'L', 'PDI']['LRI', 'ON', 'L', 'PDI']Passed
control layout['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']Passed
control layout['R', 'WS', 'R', 'L']['R', 'WS', 'R', 'L']Passed

SHA-256 / 8b11d3a6c017476280984956199f7c3d86ea92196b7f7f7c542be2eed563261f

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    types, sos = x
    t = list(types)
    n = len(t)
    prev = sos
    for i in range(n):
        if t[i] == 'NSM':
            t[i] = 'ON' if prev in ('LRI', 'RLI', 'FSI', 'PDI') else prev
        prev = t[i]
    last_strong = sos
    for i in range(n):
        if t[i] in ('L', 'R', 'AL'):
            last_strong = t[i]
        elif t[i] == 'EN' and last_strong == 'AL':
            t[i] = 'AN'
    t = ['R' if c == 'AL' else c for c in t]
    for i in range(1, n - 1):
        if t[i] == 'ES' and t[i - 1] == 'EN' and t[i + 1] == 'EN':
            t[i] = 'EN'
        elif t[i] == 'CS' and t[i - 1] == t[i + 1] == 'EN':
            t[i] = t[i - 1]
    i = 0
    while i < n:
        if t[i] == 'ET':
            j = i
            while j < n and t[j] == 'ET':
                j += 1
            if (i > 0 and t[i - 1] == 'EN') or (j < n and t[j] == 'EN'):
                for k in range(i, j):
                    t[k] = 'EN'
            i = j
        else:
            i += 1
    t = ['ON' if c in ('ES', 'ET', 'CS') else c for c in t]
    strong = sos
    for i in range(n):
        if t[i] in ('L', 'R'):
            strong = t[i]
        elif t[i] == 'EN' and strong == 'L':
            t[i] = 'L'
    return t
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: common separator number types', [['AL', 'EN', 'NSM', 'R', 'NSM', 'EN', 'CS', 'AN', 'EN'], 'L'], ['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('NSM chain after R', [['R', 'NSM', 'NSM', 'EN'], 'L'], ['R', 'R', 'R', 'EN']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['L', 'AN', 'L', 'PDI', 'ET', 'AL', 'NSM'], 'L'], ['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']), ('control layout', [['AL', 'WS', 'AL', 'L'], 'L'], ['R', 'WS', 'R', 'L'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'ON', 'LRI'], 'L'], ['ON', 'L', 'ON', 'AN', 'ON', 'LRI']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['L', 'AL', 'PDI', 'LRI', 'AL', 'EN', 'CS', 'EN', 'WS', 'CS'], 'R'], ['L', 'R', 'PDI', 'LRI', 'R', 'AN', 'AN', 'AN', 'WS', 'ON']), ('NSM chain after R', [['R', 'NSM', 'NSM', 'EN'], 'L'], ['R', 'R', 'R', 'EN']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('control layout', [['AL', 'ET', 'L', 'L', 'ET', 'CS', 'NSM', 'ET', 'ON', 'L'], 'R'], ['R', 'ON', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L']), ('control layout', [['L', 'L', 'AN', 'EN'], 'L'], ['L', 'L', 'AN', 'L'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'L', 'ON', 'AL'], 'L'], ['ON', 'L', 'ON', 'AN', 'L', 'ON', 'R']), ('regression: common separator number types', [['AL', 'EN', 'NSM', 'R', 'NSM', 'EN', 'CS', 'AN', 'EN'], 'L'], ['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']), ('partial-repair probe', [['EN', 'EN', 'AL', 'EN', 'CS', 'EN', 'PDI', 'ON', 'AN'], 'R'], ['EN', 'EN', 'R', 'AN', 'AN', 'AN', 'PDI', 'ON', 'AN']), ('partial-repair probe', [['L', 'AL', 'PDI', 'LRI', 'AL', 'EN', 'CS', 'EN', 'WS', 'CS'], 'R'], ['L', 'R', 'PDI', 'LRI', 'R', 'AN', 'AN', 'AN', 'WS', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['R', 'PDI', 'L', 'L', 'NSM', 'AL', 'EN', 'AN'], 'R'], ['R', 'PDI', 'L', 'L', 'L', 'R', 'AN', 'AN']), ('control layout', [['AL', 'NSM', 'CS', 'CS', 'AL'], 'L'], ['R', 'R', 'ON', 'ON', 'R'])], [('regression: common separator number types', [['AN', 'AN', 'CS', 'EN', 'ON', 'EN', 'EN'], 'R'], ['AN', 'AN', 'ON', 'EN', 'ON', 'EN', 'EN']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('plus between numbers', [['EN', 'ES', 'EN', 'R'], 'R'], ['EN', 'EN', 'EN', 'R']), ('control layout', [['ES', 'EN', 'ET', 'ON', 'WS', 'ET', 'AL', 'NSM', 'PDI', 'AL'], 'L'], ['ON', 'L', 'L', 'ON', 'WS', 'ON', 'R', 'R', 'PDI', 'R']), ('control layout', [['ET', 'AL', 'ES', 'NSM', 'ES', 'EN', 'PDI', 'WS', 'AN', 'CS'], 'R'], ['ON', 'R', 'ON', 'ON', 'ON', 'AN', 'PDI', 'WS', 'AN', 'ON'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'L', 'ON', 'AL'], 'L'], ['ON', 'L', 'ON', 'AN', 'L', 'ON', 'R']), ('regression: common separator number types', [['R', 'L', 'AN', 'CS', 'EN', 'CS', 'NSM', 'ES', 'AL'], 'L'], ['R', 'L', 'AN', 'ON', 'L', 'ON', 'ON', 'ON', 'R']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['L', 'LRI', 'EN'], 'L'], ['L', 'LRI', 'L']), ('control layout', [['AL', 'PDI', 'ES', 'EN', 'L', 'R', 'WS', 'ES', 'CS', 'CS'], 'L'], ['R', 'PDI', 'ON', 'AN', 'L', 'R', 'WS', 'ON', 'ON', 'ON'])]]
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: common separator number types['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']Passed
regression: common separator number types['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']Passed
partial-repair probe['AN', 'ON', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']Failed
Arabic number context['R', 'AN', 'ON', 'AN']['R', 'AN', 'AN', 'AN']Failed
NSM chain after R['R', 'R', 'R', 'EN']['R', 'R', 'R', 'EN']Passed
NSM after isolate initiator['LRI', 'ON', 'L', 'PDI']['LRI', 'ON', 'L', 'PDI']Passed
control layout['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']Passed
control layout['R', 'WS', 'R', 'L']['R', 'WS', 'R', 'L']Passed

SHA-256 / f9762febf73b415573e40f331337a70eae939174755b6698104aa6dc22393cac

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    types, sos = x
    t = list(types)
    n = len(t)
    prev = sos
    for i in range(n):
        if t[i] == 'NSM':
            t[i] = 'ON' if prev in ('LRI', 'RLI', 'FSI', 'PDI') else prev
        prev = t[i]
    last_strong = sos
    for i in range(n):
        if t[i] in ('L', 'R', 'AL'):
            last_strong = t[i]
        elif t[i] == 'EN' and last_strong == 'AL':
            t[i] = 'AN'
    t = ['R' if c == 'AL' else c for c in t]
    for i in range(1, n - 1):
        if t[i] == 'ES' and t[i - 1] == 'EN' and t[i + 1] == 'EN':
            t[i] = 'EN'
        elif t[i] == 'CS' and t[i - 1] == t[i + 1] and t[i - 1] in ('EN', 'AN'):
            t[i] = t[i - 1]
    i = 0
    while i < n:
        if t[i] == 'ET':
            j = i
            while j < n and t[j] == 'ET':
                j += 1
            if (i > 0 and t[i - 1] == 'EN') or (j < n and t[j] == 'EN'):
                for k in range(i, j):
                    t[k] = 'EN'
            i = j
        else:
            i += 1
    t = ['ON' if c in ('ES', 'ET', 'CS') else c for c in t]
    strong = sos
    for i in range(n):
        if t[i] in ('L', 'R'):
            strong = t[i]
        elif t[i] == 'EN' and strong == 'L':
            t[i] = 'L'
    return t
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: common separator number types', [['AL', 'EN', 'NSM', 'R', 'NSM', 'EN', 'CS', 'AN', 'EN'], 'L'], ['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('NSM chain after R', [['R', 'NSM', 'NSM', 'EN'], 'L'], ['R', 'R', 'R', 'EN']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['L', 'AN', 'L', 'PDI', 'ET', 'AL', 'NSM'], 'L'], ['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']), ('control layout', [['AL', 'WS', 'AL', 'L'], 'L'], ['R', 'WS', 'R', 'L'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'ON', 'LRI'], 'L'], ['ON', 'L', 'ON', 'AN', 'ON', 'LRI']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['L', 'AL', 'PDI', 'LRI', 'AL', 'EN', 'CS', 'EN', 'WS', 'CS'], 'R'], ['L', 'R', 'PDI', 'LRI', 'R', 'AN', 'AN', 'AN', 'WS', 'ON']), ('NSM chain after R', [['R', 'NSM', 'NSM', 'EN'], 'L'], ['R', 'R', 'R', 'EN']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('control layout', [['AL', 'ET', 'L', 'L', 'ET', 'CS', 'NSM', 'ET', 'ON', 'L'], 'R'], ['R', 'ON', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L']), ('control layout', [['L', 'L', 'AN', 'EN'], 'L'], ['L', 'L', 'AN', 'L'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'L', 'ON', 'AL'], 'L'], ['ON', 'L', 'ON', 'AN', 'L', 'ON', 'R']), ('regression: common separator number types', [['AL', 'EN', 'NSM', 'R', 'NSM', 'EN', 'CS', 'AN', 'EN'], 'L'], ['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']), ('partial-repair probe', [['EN', 'EN', 'AL', 'EN', 'CS', 'EN', 'PDI', 'ON', 'AN'], 'R'], ['EN', 'EN', 'R', 'AN', 'AN', 'AN', 'PDI', 'ON', 'AN']), ('partial-repair probe', [['L', 'AL', 'PDI', 'LRI', 'AL', 'EN', 'CS', 'EN', 'WS', 'CS'], 'R'], ['L', 'R', 'PDI', 'LRI', 'R', 'AN', 'AN', 'AN', 'WS', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['R', 'PDI', 'L', 'L', 'NSM', 'AL', 'EN', 'AN'], 'R'], ['R', 'PDI', 'L', 'L', 'L', 'R', 'AN', 'AN']), ('control layout', [['AL', 'NSM', 'CS', 'CS', 'AL'], 'L'], ['R', 'R', 'ON', 'ON', 'R'])], [('regression: common separator number types', [['AN', 'AN', 'CS', 'EN', 'ON', 'EN', 'EN'], 'R'], ['AN', 'AN', 'ON', 'EN', 'ON', 'EN', 'EN']), ('regression: common separator number types', [['L', 'ET', 'AN', 'CS', 'EN', 'AL', 'NSM', 'AN', 'L', 'AN', 'PDI'], 'R'], ['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('plus between numbers', [['EN', 'ES', 'EN', 'R'], 'R'], ['EN', 'EN', 'EN', 'R']), ('control layout', [['ES', 'EN', 'ET', 'ON', 'WS', 'ET', 'AL', 'NSM', 'PDI', 'AL'], 'L'], ['ON', 'L', 'L', 'ON', 'WS', 'ON', 'R', 'R', 'PDI', 'R']), ('control layout', [['ET', 'AL', 'ES', 'NSM', 'ES', 'EN', 'PDI', 'WS', 'AN', 'CS'], 'R'], ['ON', 'R', 'ON', 'ON', 'ON', 'AN', 'PDI', 'WS', 'AN', 'ON'])], [('regression: common separator number types', [['ES', 'EN', 'CS', 'AN', 'L', 'ON', 'AL'], 'L'], ['ON', 'L', 'ON', 'AN', 'L', 'ON', 'R']), ('regression: common separator number types', [['R', 'L', 'AN', 'CS', 'EN', 'CS', 'NSM', 'ES', 'AL'], 'L'], ['R', 'L', 'AN', 'ON', 'L', 'ON', 'ON', 'ON', 'R']), ('Arabic number context', [['AL', 'EN', 'CS', 'EN'], 'L'], ['R', 'AN', 'AN', 'AN']), ('partial-repair probe', [['AN', 'CS', 'AN', 'AL', 'EN', 'PDI', 'R', 'AL', 'ET'], 'R'], ['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']), ('percent after number', [['EN', 'ET', 'ET', 'L'], 'R'], ['EN', 'EN', 'EN', 'L']), ('NSM after isolate initiator', [['LRI', 'NSM', 'L', 'PDI'], 'R'], ['LRI', 'ON', 'L', 'PDI']), ('control layout', [['L', 'LRI', 'EN'], 'L'], ['L', 'LRI', 'L']), ('control layout', [['AL', 'PDI', 'ES', 'EN', 'L', 'R', 'WS', 'ES', 'CS', 'CS'], 'L'], ['R', 'PDI', 'ON', 'AN', 'L', 'R', 'WS', 'ON', 'ON', 'ON'])]]
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: common separator number types['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']['R', 'AN', 'AN', 'R', 'R', 'EN', 'ON', 'AN', 'EN']Passed
regression: common separator number types['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']['L', 'ON', 'AN', 'ON', 'L', 'R', 'R', 'AN', 'L', 'AN', 'PDI']Passed
partial-repair probe['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']['AN', 'AN', 'AN', 'R', 'AN', 'PDI', 'R', 'R', 'ON']Passed
Arabic number context['R', 'AN', 'AN', 'AN']['R', 'AN', 'AN', 'AN']Passed
NSM chain after R['R', 'R', 'R', 'EN']['R', 'R', 'R', 'EN']Passed
NSM after isolate initiator['LRI', 'ON', 'L', 'PDI']['LRI', 'ON', 'L', 'PDI']Passed
control layout['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']['L', 'AN', 'L', 'PDI', 'ON', 'R', 'R']Passed
control layout['R', 'WS', 'R', 'L']['R', 'WS', 'R', 'L']Passed

SHA-256 / db2d9bfe63a11cee7ac31df9ac6039b84508cb7520a6b9088f91e6547f8282e9

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

Case digest / faf8b650484d30bdba871147550ff3250751a4f32f7440af324a840ca0a4d701