FAILURE MAP
← Case archive

FA-82636 / Chess move legality / Open access

Rank disambiguator compared with the file letter · case 01

"R1a3" is reported illegal although the a1 rook can play it.

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

ROOT CAUSE

Every disambiguation character is matched against the origin file.

VERIFIED REPAIR

Letters filter by file and digits filter by rank.

Unsuccessful approach: Ignoring file letters makes "Nbd2" ambiguous.

Case contract

Input: {"board", "white": side to move, "san"} for a non-castling move. Check suffixes + and # are ignored. Resolve the SAN to the unique origin square of a pseudo-legal move (sliders need a clear path; pawns push one or, from their start rank through an empty square, two; pawns capture one step diagonally forward). "x" requires an enemy piece on the target and its absence requires an empty target. File/rank disambiguators filter candidates. Return the origin, "ambiguous" or "illegal".

Why this case matters

PGN import and move entry depend on resolving SAN to exactly one move.

1 / The failure

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

N = 1
observations = []
def solve(x):
    b=x['board']; white=x['white']; s=x['san'].rstrip('+#')
    def c(q): return ord(q[0])-97,int(q[1])-1
    def n(f,r): return chr(97+f)+str(r+1)
    piece=s[0] if s[0] in 'NBRQK' else 'P'
    body=s[1:] if piece!='P' else s
    capture='x' in body
    body=body.replace('x','')
    to=body[-2:]; dis=body[:-2]
    tf,tr=c(to)
    occ=b.get(to)
    if capture and (occ is None or occ.isupper()==white): return 'illegal'
    if not capture and occ is not None: return 'illegal'
    letter=piece if white else piece.lower()
    cands=[]
    for q,v in b.items():
        if v!=letter: continue
        f,r=c(q)
        if piece=='P':
            d=1 if white else -1
            if capture:
                ok=abs(tf-f)==1 and tr-r==d
            else:
                ok=tf==f and (tr-r==d or (tr-r==2*d and r==(1 if white else 6) and n(f,r+d) not in b))
        elif piece=='N':
            ok=(abs(tf-f),abs(tr-r)) in ((1,2),(2,1))
        elif piece=='K':
            ok=max(abs(tf-f),abs(tr-r))==1
        else:
            df,dr=tf-f,tr-r
            orth=(df==0)!=(dr==0); diag=abs(df)==abs(dr) and df!=0
            ok=(piece=='R' and orth) or (piece=='B' and diag) or (piece=='Q' and (orth or diag))
            if ok:
                sf=(df>0)-(df<0); sr=(dr>0)-(dr<0)
                cf,cr=f+sf,r+sr
                while (cf,cr)!=(tf,tr):
                    if n(cf,cr) in b: ok=False
                    cf+=sf; cr+=sr
        if ok and all(ch==q[0] for ch in dis):
            cands.append(q)
    if len(cands)==1: return cands[0]
    return 'ambiguous' if cands else 'illegal'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('mate suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qf7#', 'white': True}, 'h5'),
  ('check suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qe5+', 'white': True},
   'h5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('quiet move onto own',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'P', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('capture with x',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nxf3', 'white': True}, 'g1'),
  ('black double push', {'board': {'d7': 'p', 'e1': 'K', 'e8': 'k'}, 'san': 'd5', 'white': False},
   'd7'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('black double push', {'board': {'d7': 'p', 'e1': 'K', 'e8': 'k'}, 'san': 'd5', 'white': False},
   'd7'),
  ('white double push', {'board': {'e1': 'K', 'e2': 'P', 'e8': 'k'}, 'san': 'e4', 'white': True},
   'e2'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4'),
  ('backward pawn capture',
   {'board': {'d3': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd3', 'white': True},
   'illegal'),
  ('black pawn capture',
   {'board': {'d4': 'P', 'e1': 'K', 'e5': 'p', 'e8': 'k'}, 'san': 'exd4', 'white': False}, 'e5')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4'),
  ('black pawn capture',
   {'board': {'d4': 'P', 'e1': 'K', 'e5': 'p', 'e8': 'k'}, 'san': 'exd4', 'white': False}, 'e5'),
  ('rook blocked candidate',
   {'board': {'a1': 'R', 'c1': 'B', 'e2': 'K', 'e8': 'k', 'h1': 'R'}, 'san': 'Rd1', 'white': True},
   'h1'),
  ('bishop simple', {'board': {'c1': 'B', 'e1': 'K', 'e8': 'k'}, 'san': 'Bg5', 'white': True},
   'c1')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('mate suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qf7#', 'white': True}, 'h5'),
  ('check suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qe5+', 'white': True},
   'h5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'error:' + type(exc).__name__
    check(label, actual, 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
knight file disambiguationb1b1Passed
knight ambiguousambiguousambiguousPassed
rook rank disambiguationillegala1Failed
rook rank disambiguation upperillegala5Failed
mate suffixh5h5Passed
check suffixh5h5Passed
quiet move onto enemyillegalillegalPassed
pawn capture filee4e4Passed

SHA-256 / bcb3142755d67e62557f1d2d9901e5b7e2a6dfe6adb9d084493e1b3a38f761cc

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    b=x['board']; white=x['white']; s=x['san'].rstrip('+#')
    def c(q): return ord(q[0])-97,int(q[1])-1
    def n(f,r): return chr(97+f)+str(r+1)
    piece=s[0] if s[0] in 'NBRQK' else 'P'
    body=s[1:] if piece!='P' else s
    capture='x' in body
    body=body.replace('x','')
    to=body[-2:]; dis=body[:-2]
    tf,tr=c(to)
    occ=b.get(to)
    if capture and (occ is None or occ.isupper()==white): return 'illegal'
    if not capture and occ is not None: return 'illegal'
    letter=piece if white else piece.lower()
    cands=[]
    for q,v in b.items():
        if v!=letter: continue
        f,r=c(q)
        if piece=='P':
            d=1 if white else -1
            if capture:
                ok=abs(tf-f)==1 and tr-r==d
            else:
                ok=tf==f and (tr-r==d or (tr-r==2*d and r==(1 if white else 6) and n(f,r+d) not in b))
        elif piece=='N':
            ok=(abs(tf-f),abs(tr-r)) in ((1,2),(2,1))
        elif piece=='K':
            ok=max(abs(tf-f),abs(tr-r))==1
        else:
            df,dr=tf-f,tr-r
            orth=(df==0)!=(dr==0); diag=abs(df)==abs(dr) and df!=0
            ok=(piece=='R' and orth) or (piece=='B' and diag) or (piece=='Q' and (orth or diag))
            if ok:
                sf=(df>0)-(df<0); sr=(dr>0)-(dr<0)
                cf,cr=f+sf,r+sr
                while (cf,cr)!=(tf,tr):
                    if n(cf,cr) in b: ok=False
                    cf+=sf; cr+=sr
        if ok and all(ch==q[1] if ch.isdigit() else True for ch in dis):
            cands.append(q)
    if len(cands)==1: return cands[0]
    return 'ambiguous' if cands else 'illegal'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('mate suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qf7#', 'white': True}, 'h5'),
  ('check suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qe5+', 'white': True},
   'h5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('quiet move onto own',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'P', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('capture with x',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nxf3', 'white': True}, 'g1'),
  ('black double push', {'board': {'d7': 'p', 'e1': 'K', 'e8': 'k'}, 'san': 'd5', 'white': False},
   'd7'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('black double push', {'board': {'d7': 'p', 'e1': 'K', 'e8': 'k'}, 'san': 'd5', 'white': False},
   'd7'),
  ('white double push', {'board': {'e1': 'K', 'e2': 'P', 'e8': 'k'}, 'san': 'e4', 'white': True},
   'e2'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4'),
  ('backward pawn capture',
   {'board': {'d3': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd3', 'white': True},
   'illegal'),
  ('black pawn capture',
   {'board': {'d4': 'P', 'e1': 'K', 'e5': 'p', 'e8': 'k'}, 'san': 'exd4', 'white': False}, 'e5')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4'),
  ('black pawn capture',
   {'board': {'d4': 'P', 'e1': 'K', 'e5': 'p', 'e8': 'k'}, 'san': 'exd4', 'white': False}, 'e5'),
  ('rook blocked candidate',
   {'board': {'a1': 'R', 'c1': 'B', 'e2': 'K', 'e8': 'k', 'h1': 'R'}, 'san': 'Rd1', 'white': True},
   'h1'),
  ('bishop simple', {'board': {'c1': 'B', 'e1': 'K', 'e8': 'k'}, 'san': 'Bg5', 'white': True},
   'c1')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('mate suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qf7#', 'white': True}, 'h5'),
  ('check suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qe5+', 'white': True},
   'h5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'error:' + type(exc).__name__
    check(label, actual, 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
knight file disambiguationambiguousb1Failed
knight ambiguousambiguousambiguousPassed
rook rank disambiguationa1a1Passed
rook rank disambiguation uppera5a5Passed
mate suffixh5h5Passed
check suffixh5h5Passed
quiet move onto enemyillegalillegalPassed
pawn capture fileambiguouse4Failed

SHA-256 / 60bc2c1cd5d27b1956ca7c022b93131c0a6915080131ef08a9a2114e7b22d4d5

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    b=x['board']; white=x['white']; s=x['san'].rstrip('+#')
    def c(q): return ord(q[0])-97,int(q[1])-1
    def n(f,r): return chr(97+f)+str(r+1)
    piece=s[0] if s[0] in 'NBRQK' else 'P'
    body=s[1:] if piece!='P' else s
    capture='x' in body
    body=body.replace('x','')
    to=body[-2:]; dis=body[:-2]
    tf,tr=c(to)
    occ=b.get(to)
    if capture and (occ is None or occ.isupper()==white): return 'illegal'
    if not capture and occ is not None: return 'illegal'
    letter=piece if white else piece.lower()
    cands=[]
    for q,v in b.items():
        if v!=letter: continue
        f,r=c(q)
        if piece=='P':
            d=1 if white else -1
            if capture:
                ok=abs(tf-f)==1 and tr-r==d
            else:
                ok=tf==f and (tr-r==d or (tr-r==2*d and r==(1 if white else 6) and n(f,r+d) not in b))
        elif piece=='N':
            ok=(abs(tf-f),abs(tr-r)) in ((1,2),(2,1))
        elif piece=='K':
            ok=max(abs(tf-f),abs(tr-r))==1
        else:
            df,dr=tf-f,tr-r
            orth=(df==0)!=(dr==0); diag=abs(df)==abs(dr) and df!=0
            ok=(piece=='R' and orth) or (piece=='B' and diag) or (piece=='Q' and (orth or diag))
            if ok:
                sf=(df>0)-(df<0); sr=(dr>0)-(dr<0)
                cf,cr=f+sf,r+sr
                while (cf,cr)!=(tf,tr):
                    if n(cf,cr) in b: ok=False
                    cf+=sf; cr+=sr
        if ok and all((ch==q[0]) if ch.isalpha() else (ch==q[1]) for ch in dis):
            cands.append(q)
    if len(cands)==1: return cands[0]
    return 'ambiguous' if cands else 'illegal'
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('mate suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qf7#', 'white': True}, 'h5'),
  ('check suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qe5+', 'white': True},
   'h5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('quiet move onto own',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'P', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('capture with x',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nxf3', 'white': True}, 'g1'),
  ('black double push', {'board': {'d7': 'p', 'e1': 'K', 'e8': 'k'}, 'san': 'd5', 'white': False},
   'd7'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('black double push', {'board': {'d7': 'p', 'e1': 'K', 'e8': 'k'}, 'san': 'd5', 'white': False},
   'd7'),
  ('white double push', {'board': {'e1': 'K', 'e2': 'P', 'e8': 'k'}, 'san': 'e4', 'white': True},
   'e2'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4'),
  ('backward pawn capture',
   {'board': {'d3': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd3', 'white': True},
   'illegal'),
  ('black pawn capture',
   {'board': {'d4': 'P', 'e1': 'K', 'e5': 'p', 'e8': 'k'}, 'san': 'exd4', 'white': False}, 'e5')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4'),
  ('black pawn capture',
   {'board': {'d4': 'P', 'e1': 'K', 'e5': 'p', 'e8': 'k'}, 'san': 'exd4', 'white': False}, 'e5'),
  ('rook blocked candidate',
   {'board': {'a1': 'R', 'c1': 'B', 'e2': 'K', 'e8': 'k', 'h1': 'R'}, 'san': 'Rd1', 'white': True},
   'h1'),
  ('bishop simple', {'board': {'c1': 'B', 'e1': 'K', 'e8': 'k'}, 'san': 'Bg5', 'white': True},
   'c1')],
 [('knight file disambiguation',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nbd2', 'white': True}, 'b1'),
  ('knight ambiguous',
   {'board': {'b1': 'N', 'e1': 'K', 'e8': 'k', 'f3': 'N'}, 'san': 'Nd2', 'white': True},
   'ambiguous'),
  ('rook rank disambiguation',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R1a3', 'white': True}, 'a1'),
  ('rook rank disambiguation upper',
   {'board': {'a1': 'R', 'a5': 'R', 'e1': 'K', 'e8': 'k'}, 'san': 'R5a3', 'white': True}, 'a5'),
  ('mate suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qf7#', 'white': True}, 'h5'),
  ('check suffix', {'board': {'e1': 'K', 'e8': 'k', 'h5': 'Q'}, 'san': 'Qe5+', 'white': True},
   'h5'),
  ('quiet move onto enemy',
   {'board': {'e1': 'K', 'e8': 'k', 'f3': 'p', 'g1': 'N'}, 'san': 'Nf3', 'white': True},
   'illegal'),
  ('pawn capture file',
   {'board': {'c4': 'P', 'd5': 'p', 'e1': 'K', 'e4': 'P', 'e8': 'k'}, 'san': 'exd5', 'white': True},
   'e4')]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'error:' + type(exc).__name__
    check(label, actual, 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
knight file disambiguationb1b1Passed
knight ambiguousambiguousambiguousPassed
rook rank disambiguationa1a1Passed
rook rank disambiguation uppera5a5Passed
mate suffixh5h5Passed
check suffixh5h5Passed
quiet move onto enemyillegalillegalPassed
pawn capture filee4e4Passed

SHA-256 / dfb6424c8d9c1fd49f0b5ed8b2ebeec25c93be7a077b57b8dbbbed1ac39dd2ec

Verification & scope

A deterministic toy model with an explicitly stated contract; boards are {square: piece} with white upper-case and black lower-case pieces; no claim of full FIDE Laws conformance. 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:13.995630+00:00.

Case digest / b9aa5a088e239247a082596bb0cf43331d5e8c900f4edc9f3613f7a9b65ce2ab