FAILURE MAP
← Case archive

FA-83631 / Card game rule engines / Open access

The last bidder becomes declarer · case 01

When partner first named the strain, the raiser is reported as declarer.

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

ROOT CAUSE

The declarer is taken from the last bid rather than the first mention of the strain.

THE FAILURE

The declarer is taken from the last bid rather than the first mention of the strain.

Unsuccessful approach: Overwriting the first-mention table each time keeps the most recent mention instead.

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], last_bidder]
            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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]]], [[['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', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['P', '1D', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 3]]], [[['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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['1H', 'P', '1S', 'P', '2S', 'P', 'P', 'P'], ['contract', '2S', 2]]], [[['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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]]], [[['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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['P', '1D', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 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['contract', '2H', 0]['contract', '2H', 2]Failed
auction case 7['contract', '2H', 0]['contract', '2H', 2]Failed

SHA-256 / 59312ff004f4c75a70457ebf6fd27c01b1d47ec01afc4770d2de43f598828a10

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[(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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]]], [[['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', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['P', '1D', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 3]]], [[['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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['1H', 'P', '1S', 'P', '2S', 'P', 'P', 'P'], ['contract', '2S', 2]]], [[['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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]]], [[['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]], [['1C', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 2]], [['P', '1D', 'P', '1H', 'P', '2H', 'P', 'P', 'P'], ['contract', '2H', 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['contract', '2H', 0]['contract', '2H', 2]Failed
auction case 7['contract', '2H', 0]['contract', '2H', 2]Failed

SHA-256 / 9ce17404154ed29bd361a5fc15cf0c4b11c85de23f83a2b86f397ed55f4453cf

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

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

Case digest / 584e0a56ee872b4ee0afe6b7bd93777ab172cc12bf66546fa5515b5eb6edeac2