FAILURE MAP
← Case archive

FA-83636 / Card game rule engines / Open access

A same-level lower strain bid is accepted · case 01

1H over 1S is accepted as sufficient.

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

ROOT CAUSE

Only the level is compared.

VERIFIED REPAIR

Compare (level, strain) lexicographically.

Unsuccessful approach: Requiring both level and strain to be no higher accepts a lower level in a higher strain.

Case contract

Input list of calls (bids like "2H"/"3N", "P", "X", "XX") starting with dealer seat 0. Each bid must outrank the previous bid (level, then C<D<H<S<N). X requires the last non-pass call to be an opponent bid; XX an opponent X. Four initial passes -> ["passed out", None, None]; three passes after a bid -> ["contract", bid+X/XX, declarer] where declarer is the first player of the declaring side to name the strain. Illegal call -> ["illegal", index, None]; unfinished -> ["open", None, None].

Why this case matters

Card-game engines, scoring apps and online tables apply this rule automatically on every hand.

1 / The failure

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

N = 1
observations = []
def solve(x):
    calls = x
    strains = 'CDHSN'
    last_bid = None
    last_bidder = None
    state = 0
    last_call = None
    last_caller = None
    passes = 0
    first = {}
    for i, call in enumerate(calls):
        seat = i % 4
        if call == 'P':
            passes += 1
            if last_bid is None and passes == 4:
                return ['passed out', None, None]
            if last_bid is not None and passes == 3:
                side = last_bidder % 2
                return ['contract', last_bid + ['', 'X', 'XX'][state], first[(side, last_bid[1])]]
            continue
        if call == 'X':
            if last_call is None or last_call not in ('bid',) or (last_caller - seat) % 2 == 0:
                return ['illegal', i, None]
            state = 1
        elif call == 'XX':
            if last_call != 'X' or (last_caller - seat) % 2 == 0:
                return ['illegal', i, None]
            state = 2
        else:
            rank = (int(call[0]), strains.index(call[1]))
            if last_bid is not None and int(call[0]) < int(last_bid[0]):
                return ['illegal', i, None]
            last_bid = call
            last_bidder = seat
            state = 0
            first.setdefault((seat % 2, call[1]), seat)
        passes = 0
        last_call = 'bid' if call not in ('X', 'XX') else call
        last_caller = seat
    return ['open', None, None]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[['X', '1D', 'P'], ['illegal', 0, None]], [['P', 'X', '1C', 'P', 'P', 'P', 'P', '1N', '2S', '3H'], ['illegal', 1, None]], [['1S', 'P', 'P', '2C', '2N', 'P', '2S'], ['illegal', 6, None]], [['P', '1S', 'P', 'P', 'P', '2D', 'P'], ['contract', '1S', 1]], [['P', 'P'], ['open', None, None]], [['1H'], ['open', None, None]], [['P', 'XX', 'X', 'P', 'P'], ['illegal', 1, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['P', 'P', '1C', 'P', 'X', 'P', '1H', '2C', '2N'], ['illegal', 4, None]], [['1C', '1C', 'P', '1S', 'P', 'X', 'P', 'P', 'XX'], ['illegal', 1, None]], [['P', 'P', '1C', 'P'], ['open', None, None]], [['1S', 'P', 'P', '2C', 'XX', 'X'], ['illegal', 4, None]], [['XX', '1C', '1N', 'P', 'P', 'P'], ['illegal', 0, None]], [['1C', 'XX', 'P', 'X', 'P', 'P', '1D', 'X', 'P', 'P', 'P'], ['illegal', 1, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['P', 'P'], ['open', None, None]], [['1H', 'P', 'XX', 'X', '1D', '2D'], ['illegal', 2, None]], [['XX'], ['illegal', 0, None]], [['XX', 'P'], ['illegal', 0, None]], [['X', 'P', '1C', 'P', 'P', 'P', 'X'], ['illegal', 0, None]], [['P', 'X', 'XX', 'X', 'X', 'P', 'X', 'P'], ['illegal', 1, None]], [['1S', '1H', '2D', 'P', 'P', '3C'], ['illegal', 1, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['1H', '1S', 'P', '2H', 'P', 'P', '2N', 'X', 'P', '3D'], ['open', None, None]], [['P', 'X', 'X', '1H', 'P', 'P', 'X', '2D', 'P', 'P'], ['illegal', 1, None]], [['P', 'P', 'X', 'P', 'X'], ['illegal', 2, None]], [['XX', 'P', 'P', 'P'], ['illegal', 0, None]], [['P', 'X'], ['illegal', 1, None]], [['X', '1D', 'P', '2C', 'P', 'P', 'P', 'XX', '2D', 'X', '2H', 'P', '2S'], ['illegal', 0, None]], [['1S', '2D', '2H', '3D', '3C', 'P', '3C'], ['illegal', 4, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['1C', '1C', 'P', '1S', 'P', 'X', 'P', 'P', 'XX'], ['illegal', 1, None]], [['1S', 'P', 'XX', '2D', '2S', 'P'], ['illegal', 2, None]], [['P', 'P', '1D', 'P', 'P', 'P', 'P', '1H', 'X', '1N', 'P'], ['contract', '1D', 2]], [['P', 'P', '1H', 'XX', '1S', '2C', '2H', '2D', 'P', 'P', '3C', 'XX', 'P'], ['illegal', 3, None]], [['P', '1C', 'XX', 'P', 'P', 'P', 'P', '1N', 'P'], ['illegal', 2, None]], [['P', '1D', 'X', '2C', '2S', 'P', '3D', 'P', 'P', 'P'], ['contract', '3D', 2]], [['XX', 'P', 'X', 'XX', '1C', 'P', 'XX', 'P', 'P', 'XX', 'X', '1H'], ['illegal', 0, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("auction case %d" % i, 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
auction case 0['illegal', 0, None]['illegal', 0, None]Passed
auction case 1['illegal', 1, None]['illegal', 1, None]Passed
auction case 2['open', None, None]['illegal', 6, None]Failed
auction case 3['contract', '1S', 1]['contract', '1S', 1]Passed
auction case 4['open', None, None]['open', None, None]Passed
auction case 5['open', None, None]['open', None, None]Passed
auction case 6['illegal', 1, None]['illegal', 1, None]Passed
auction case 7['illegal', 2, None]['illegal', 2, None]Passed

SHA-256 / 46f05fd38680a7a5c0a508f9058592d954621366c055dcc48d4fcb6e19099f2b

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    calls = x
    strains = 'CDHSN'
    last_bid = None
    last_bidder = None
    state = 0
    last_call = None
    last_caller = None
    passes = 0
    first = {}
    for i, call in enumerate(calls):
        seat = i % 4
        if call == 'P':
            passes += 1
            if last_bid is None and passes == 4:
                return ['passed out', None, None]
            if last_bid is not None and passes == 3:
                side = last_bidder % 2
                return ['contract', last_bid + ['', 'X', 'XX'][state], first[(side, last_bid[1])]]
            continue
        if call == 'X':
            if last_call is None or last_call not in ('bid',) or (last_caller - seat) % 2 == 0:
                return ['illegal', i, None]
            state = 1
        elif call == 'XX':
            if last_call != 'X' or (last_caller - seat) % 2 == 0:
                return ['illegal', i, None]
            state = 2
        else:
            rank = (int(call[0]), strains.index(call[1]))
            if last_bid is not None and rank[0] <= int(last_bid[0]) and rank[1] <= strains.index(last_bid[1]):
                return ['illegal', i, None]
            last_bid = call
            last_bidder = seat
            state = 0
            first.setdefault((seat % 2, call[1]), seat)
        passes = 0
        last_call = 'bid' if call not in ('X', 'XX') else call
        last_caller = seat
    return ['open', None, None]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[['X', '1D', 'P'], ['illegal', 0, None]], [['P', 'X', '1C', 'P', 'P', 'P', 'P', '1N', '2S', '3H'], ['illegal', 1, None]], [['1S', 'P', 'P', '2C', '2N', 'P', '2S'], ['illegal', 6, None]], [['P', '1S', 'P', 'P', 'P', '2D', 'P'], ['contract', '1S', 1]], [['P', 'P'], ['open', None, None]], [['1H'], ['open', None, None]], [['P', 'XX', 'X', 'P', 'P'], ['illegal', 1, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['P', 'P', '1C', 'P', 'X', 'P', '1H', '2C', '2N'], ['illegal', 4, None]], [['1C', '1C', 'P', '1S', 'P', 'X', 'P', 'P', 'XX'], ['illegal', 1, None]], [['P', 'P', '1C', 'P'], ['open', None, None]], [['1S', 'P', 'P', '2C', 'XX', 'X'], ['illegal', 4, None]], [['XX', '1C', '1N', 'P', 'P', 'P'], ['illegal', 0, None]], [['1C', 'XX', 'P', 'X', 'P', 'P', '1D', 'X', 'P', 'P', 'P'], ['illegal', 1, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['P', 'P'], ['open', None, None]], [['1H', 'P', 'XX', 'X', '1D', '2D'], ['illegal', 2, None]], [['XX'], ['illegal', 0, None]], [['XX', 'P'], ['illegal', 0, None]], [['X', 'P', '1C', 'P', 'P', 'P', 'X'], ['illegal', 0, None]], [['P', 'X', 'XX', 'X', 'X', 'P', 'X', 'P'], ['illegal', 1, None]], [['1S', '1H', '2D', 'P', 'P', '3C'], ['illegal', 1, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['1H', '1S', 'P', '2H', 'P', 'P', '2N', 'X', 'P', '3D'], ['open', None, None]], [['P', 'X', 'X', '1H', 'P', 'P', 'X', '2D', 'P', 'P'], ['illegal', 1, None]], [['P', 'P', 'X', 'P', 'X'], ['illegal', 2, None]], [['XX', 'P', 'P', 'P'], ['illegal', 0, None]], [['P', 'X'], ['illegal', 1, None]], [['X', '1D', 'P', '2C', 'P', 'P', 'P', 'XX', '2D', 'X', '2H', 'P', '2S'], ['illegal', 0, None]], [['1S', '2D', '2H', '3D', '3C', 'P', '3C'], ['illegal', 4, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['1C', '1C', 'P', '1S', 'P', 'X', 'P', 'P', 'XX'], ['illegal', 1, None]], [['1S', 'P', 'XX', '2D', '2S', 'P'], ['illegal', 2, None]], [['P', 'P', '1D', 'P', 'P', 'P', 'P', '1H', 'X', '1N', 'P'], ['contract', '1D', 2]], [['P', 'P', '1H', 'XX', '1S', '2C', '2H', '2D', 'P', 'P', '3C', 'XX', 'P'], ['illegal', 3, None]], [['P', '1C', 'XX', 'P', 'P', 'P', 'P', '1N', 'P'], ['illegal', 2, None]], [['P', '1D', 'X', '2C', '2S', 'P', '3D', 'P', 'P', 'P'], ['contract', '3D', 2]], [['XX', 'P', 'X', 'XX', '1C', 'P', 'XX', 'P', 'P', 'XX', 'X', '1H'], ['illegal', 0, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("auction case %d" % i, 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
auction case 0['illegal', 0, None]['illegal', 0, None]Passed
auction case 1['illegal', 1, None]['illegal', 1, None]Passed
auction case 2['illegal', 6, None]['illegal', 6, None]Passed
auction case 3['contract', '1S', 1]['contract', '1S', 1]Passed
auction case 4['open', None, None]['open', None, None]Passed
auction case 5['open', None, None]['open', None, None]Passed
auction case 6['illegal', 1, None]['illegal', 1, None]Passed
auction case 7['open', None, None]['illegal', 2, None]Failed

SHA-256 / dad06c5b1954eede270246d5776c379af83ea2287a2ca1b7079337b88aa45cce

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    calls = x
    strains = 'CDHSN'
    last_bid = None
    last_bidder = None
    state = 0
    last_call = None
    last_caller = None
    passes = 0
    first = {}
    for i, call in enumerate(calls):
        seat = i % 4
        if call == 'P':
            passes += 1
            if last_bid is None and passes == 4:
                return ['passed out', None, None]
            if last_bid is not None and passes == 3:
                side = last_bidder % 2
                return ['contract', last_bid + ['', 'X', 'XX'][state], first[(side, last_bid[1])]]
            continue
        if call == 'X':
            if last_call is None or last_call not in ('bid',) or (last_caller - seat) % 2 == 0:
                return ['illegal', i, None]
            state = 1
        elif call == 'XX':
            if last_call != 'X' or (last_caller - seat) % 2 == 0:
                return ['illegal', i, None]
            state = 2
        else:
            rank = (int(call[0]), strains.index(call[1]))
            if last_bid is not None and rank <= (int(last_bid[0]), strains.index(last_bid[1])):
                return ['illegal', i, None]
            last_bid = call
            last_bidder = seat
            state = 0
            first.setdefault((seat % 2, call[1]), seat)
        passes = 0
        last_call = 'bid' if call not in ('X', 'XX') else call
        last_caller = seat
    return ['open', None, None]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[['X', '1D', 'P'], ['illegal', 0, None]], [['P', 'X', '1C', 'P', 'P', 'P', 'P', '1N', '2S', '3H'], ['illegal', 1, None]], [['1S', 'P', 'P', '2C', '2N', 'P', '2S'], ['illegal', 6, None]], [['P', '1S', 'P', 'P', 'P', '2D', 'P'], ['contract', '1S', 1]], [['P', 'P'], ['open', None, None]], [['1H'], ['open', None, None]], [['P', 'XX', 'X', 'P', 'P'], ['illegal', 1, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['P', 'P', '1C', 'P', 'X', 'P', '1H', '2C', '2N'], ['illegal', 4, None]], [['1C', '1C', 'P', '1S', 'P', 'X', 'P', 'P', 'XX'], ['illegal', 1, None]], [['P', 'P', '1C', 'P'], ['open', None, None]], [['1S', 'P', 'P', '2C', 'XX', 'X'], ['illegal', 4, None]], [['XX', '1C', '1N', 'P', 'P', 'P'], ['illegal', 0, None]], [['1C', 'XX', 'P', 'X', 'P', 'P', '1D', 'X', 'P', 'P', 'P'], ['illegal', 1, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['P', 'P'], ['open', None, None]], [['1H', 'P', 'XX', 'X', '1D', '2D'], ['illegal', 2, None]], [['XX'], ['illegal', 0, None]], [['XX', 'P'], ['illegal', 0, None]], [['X', 'P', '1C', 'P', 'P', 'P', 'X'], ['illegal', 0, None]], [['P', 'X', 'XX', 'X', 'X', 'P', 'X', 'P'], ['illegal', 1, None]], [['1S', '1H', '2D', 'P', 'P', '3C'], ['illegal', 1, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['1H', '1S', 'P', '2H', 'P', 'P', '2N', 'X', 'P', '3D'], ['open', None, None]], [['P', 'X', 'X', '1H', 'P', 'P', 'X', '2D', 'P', 'P'], ['illegal', 1, None]], [['P', 'P', 'X', 'P', 'X'], ['illegal', 2, None]], [['XX', 'P', 'P', 'P'], ['illegal', 0, None]], [['P', 'X'], ['illegal', 1, None]], [['X', '1D', 'P', '2C', 'P', 'P', 'P', 'XX', '2D', 'X', '2H', 'P', '2S'], ['illegal', 0, None]], [['1S', '2D', '2H', '3D', '3C', 'P', '3C'], ['illegal', 4, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]], [[['1C', '1C', 'P', '1S', 'P', 'X', 'P', 'P', 'XX'], ['illegal', 1, None]], [['1S', 'P', 'XX', '2D', '2S', 'P'], ['illegal', 2, None]], [['P', 'P', '1D', 'P', 'P', 'P', 'P', '1H', 'X', '1N', 'P'], ['contract', '1D', 2]], [['P', 'P', '1H', 'XX', '1S', '2C', '2H', '2D', 'P', 'P', '3C', 'XX', 'P'], ['illegal', 3, None]], [['P', '1C', 'XX', 'P', 'P', 'P', 'P', '1N', 'P'], ['illegal', 2, None]], [['P', '1D', 'X', '2C', '2S', 'P', '3D', 'P', 'P', 'P'], ['contract', '3D', 2]], [['XX', 'P', 'X', 'XX', '1C', 'P', 'XX', 'P', 'P', 'XX', 'X', '1H'], ['illegal', 0, None]], [['2C', 'P', '1N'], ['illegal', 2, None]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("auction case %d" % i, 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
auction case 0['illegal', 0, None]['illegal', 0, None]Passed
auction case 1['illegal', 1, None]['illegal', 1, None]Passed
auction case 2['illegal', 6, None]['illegal', 6, None]Passed
auction case 3['contract', '1S', 1]['contract', '1S', 1]Passed
auction case 4['open', None, None]['open', None, None]Passed
auction case 5['open', None, None]['open', None, None]Passed
auction case 6['illegal', 1, None]['illegal', 1, None]Passed
auction case 7['illegal', 2, None]['illegal', 2, None]Passed

SHA-256 / 9b4bad4b0e51261b6627d1a06f9a65c843c594b4d2db3ca06e942b3a89c78945

Verification & scope

A bounded toy rule contract stated explicitly in the contract field; cards are two-character codes (rank, suit). Not a complete implementation of any published rulebook or casino table rules. 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:23.385382+00:00.

Case digest / e95ab7875f739908c658f691f54dcf0678e1dc93e07ff46c9ae7aa70c6285362