FAILURE MAP
← Case archive

FA-83621 / Card game rule engines / Open access

Passes before a double still count toward the end of the auction · case 01

An auction ends one pass after a balancing double.

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

ROOT CAUSE

The pass counter is reset only by bids.

VERIFIED REPAIR

Any non-pass call (bid, X, XX) resets the pass count.

Unsuccessful approach: Moving the reset into the bid branch still lets X and XX keep earlier passes.

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 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)
        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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, 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]], [['1H', '1S', 'P', '2H', 'P', 'P', '2N', 'X', 'P', '3D'], ['open', None, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, 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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, None]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, 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]], [['P', '1D', 'P', 'P', 'XX', 'P', 'X', 'P', 'X'], ['illegal', 4, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, None]]], [[['1D', 'XX', 'P', 'P', 'XX', 'P'], ['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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, None]], [['P', 'P', 'P', '1S', 'X', 'P', 'P', 'P'], ['contract', '1SX', 3]]]]
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['contract', '2N', 0]['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['contract', '1S', 1]['illegal', 7, None]Failed

SHA-256 / cd5de56bf5d62bbdcc0df995e952a37b596d170f4749e6dd1934ecb353d9b5fc

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 <= (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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, 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]], [['1H', '1S', 'P', '2H', 'P', 'P', '2N', 'X', 'P', '3D'], ['open', None, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, 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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, None]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, 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]], [['P', '1D', 'P', 'P', 'XX', 'P', 'X', 'P', 'X'], ['illegal', 4, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, None]]], [[['1D', 'XX', 'P', 'P', 'XX', 'P'], ['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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, None]], [['P', 'P', 'P', '1S', 'X', 'P', 'P', 'P'], ['contract', '1SX', 3]]]]
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['contract', '1SX', 1]['illegal', 7, None]Failed

SHA-256 / 90169b5e311be2962fb5f7fcf73e244fdafbf1f043e438bc47dfe735f3f23588

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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, 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]], [['1H', '1S', 'P', '2H', 'P', 'P', '2N', 'X', 'P', '3D'], ['open', None, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, 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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, None]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, 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]], [['P', '1D', 'P', 'P', 'XX', 'P', 'X', 'P', 'X'], ['illegal', 4, None]], [['1C', 'X', 'P', '1D', '1H', 'P', 'P', 'X', 'P', '2D', '2C', 'X'], ['illegal', 10, None]]], [[['1D', 'XX', 'P', 'P', 'XX', 'P'], ['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]], [['P', '1S', 'P', 'P', 'X', 'P', 'P', 'X', '2H', 'P', 'XX', 'P', '2S'], ['illegal', 7, None]], [['P', 'P', 'P', '1S', 'X', 'P', 'P', 'P'], ['contract', '1SX', 3]]]]
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', 7, None]['illegal', 7, None]Passed

SHA-256 / 602e3933763187c7123ae84600dcd295fda38e779a7377fab54873151f4e9b59

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

Case digest / aeb554da87166b0ed01d28c554940b840f03b74bb4ea85855441f776179c0888