FAILURE MAP
← Case archive

FA-83376 / Card game rule engines / Open access

Players must follow the most recent card instead of the led suit · case 01

After a discard, the next player is forced to follow the discard suit.

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

ROOT CAUSE

The suit to follow is taken from the last card in the trick.

VERIFIED REPAIR

The led suit is the suit of the first card in the trick.

Unsuccessful approach: Taking the suit of the lowest-sorted card in the trick still ignores play order.

Case contract

Input [hand, trick_so_far, hearts_broken, first_trick]. Leading: on the first trick the holder of 2C must lead it; hearts may not be led before they are broken unless the hand is all hearts (QS may be led). Following: follow the led suit if possible; on the first trick a void player may not discard hearts or QS unless nothing else is held. Return legal cards in hand order.

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):
    hand, trick, broken, first = x
    def penalty(c):
        return c[1] == 'H' or c == 'QS'
    if not trick:
        if first and '2C' in hand:
            return ['2C']
        if not broken:
            safe = [c for c in hand if c[1] != 'H']
            if safe:
                return safe
        return list(hand)
    suit = trick[-1][1]
    follow = [c for c in hand if c[1] == suit]
    if follow:
        return follow
    if first:
        clean = [c for c in hand if not penalty(c)]
        if clean:
            return clean
    return list(hand)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['4C', '8D', '9S', 'QC', 'TC', 'TS'], ['8S', 'QD', '7C'], True, False], ['9S', 'TS']], [[['5H', '6C', '7C', '9C', 'KD', 'QC', 'TS'], ['3S', '8S', '2S'], False, True], ['TS']], [[['2C', 'JS'], [], True, True], ['2C']], [[['AS', 'TH'], [], False, True], ['AS']], [[['2C', '8C', 'AS', 'KH'], [], True, False], ['2C', '8C', 'AS', 'KH']], [[['2H', '3C', '3H', 'QC'], [], True, False], ['2H', '3C', '3H', 'QC']], [[['7C'], ['4S'], False, True], ['7C']], [[['6H', '7H', '8H', 'JD', 'KC', 'KD', 'TS'], ['AC', '7D', '9S'], False, False], ['KC']]], [[[['2S', 'KS', 'TH'], ['JC', '2C'], False, True], ['2S', 'KS']], [[['2S'], ['QS'], False, False], ['2S']], [[['2H', '6C', '7H', '9C', '9S'], ['QC', '2C', '6H'], False, False], ['6C', '9C']], [[['3H', '5D', '9D'], ['3C'], True, False], ['3H', '5D', '9D']], [[['AH', 'JH', 'TH'], ['9C', 'TS'], True, False], ['AH', 'JH', 'TH']], [[['2H', '6S', '8D', 'AC', 'JD', 'QH'], ['4H', 'KC', '3S'], True, True], ['2H', 'QH']], [[['3D', '9H', 'QS'], [], False, False], ['3D', 'QS']], [[['4S', '5C', 'JC', 'KD', 'QD'], ['9H', '3C'], False, False], ['4S', '5C', 'JC', 'KD', 'QD']]], [[[['5C'], [], False, True], ['5C']], [[['5D', 'JH'], ['QH', 'KC', '8S'], True, False], ['JH']], [[['4H'], [], True, True], ['4H']], [[['3D', '5C'], [], True, False], ['3D', '5C']], [[['2H', '5H'], ['KS', '9C', '7H'], False, False], ['2H', '5H']], [[['8C', 'AC', 'KD', 'TS'], [], False, True], ['8C', 'AC', 'KD', 'TS']], [[['5D', 'AS'], ['5S', '3C'], False, False], ['AS']], [[['3H', '7C', '8S', '9S', 'JC', 'KC', 'KH', 'KS'], ['8H', '4C', '4D'], True, False], ['3H', 'KH']]], [[[['2H', '6C', '6D', '7C', 'KS', 'QC'], ['3C', '7D'], True, False], ['6C', '7C', 'QC']], [[['AC'], ['2C', '8D'], False, False], ['AC']], [[['2S', '6D'], [], True, False], ['2S', '6D']], [[['5D', 'AS'], ['5S', '3C'], False, False], ['AS']], [[['3C', '4S', '6S', '7D', '8H', 'AS', 'KC', 'KH'], [], False, False], ['3C', '4S', '6S', '7D', 'AS', 'KC']], [[['2H', '3D', '5H', '9D', 'JC', 'KS', 'QD'], [], False, False], ['3D', '9D', 'JC', 'KS', 'QD']], [[['2D', '3H', '3S', '4S', '6D', 'QC', 'QH'], ['6C', 'AH'], False, False], ['QC']], [[['6H', '6S', 'TC', 'TS'], ['6D', '4D', '2H'], False, False], ['6H', '6S', 'TC', 'TS']]], [[[['4C', '8D', '9S', 'QC', 'TC', 'TS'], ['8S', 'QD', '7C'], True, False], ['9S', 'TS']], [[['3S', '7D'], [], False, True], ['3S', '7D']], [[['2D', '3H', '3S', '4S', '6D', 'QC', 'QH'], ['6C', 'AH'], False, False], ['QC']], [[['3C', '6D', 'TH'], [], False, False], ['3C', '6D']], [[['3D', '5C', '6S', '7H', 'KH', 'TH', 'TS'], [], False, False], ['3D', '5C', '6S', 'TS']], [[['7D', '8H', 'AD', 'AS'], ['AH', '4H', '9C'], True, True], ['8H']], [[['2C', '4C', '6H', '8H', '9D', 'KC', 'TS'], ['6C', '9C'], False, False], ['2C', '4C', 'KC']], [[['2C', '3C', '5D', '6H', 'KD', 'QC', 'TC', 'TH'], ['8C', '6S'], True, True], ['2C', '3C', 'QC', 'TC']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("legal plays 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
legal plays case 0['4C', 'QC', 'TC']['9S', 'TS']Failed
legal plays case 1['TS']['TS']Passed
legal plays case 2['2C']['2C']Passed
legal plays case 3['AS']['AS']Passed
legal plays case 4['2C', '8C', 'AS', 'KH']['2C', '8C', 'AS', 'KH']Passed
legal plays case 5['2H', '3C', '3H', 'QC']['2H', '3C', '3H', 'QC']Passed
legal plays case 6['7C']['7C']Passed
legal plays case 7['TS']['KC']Failed

SHA-256 / ed6925d9a1400c7d07dff3505c5deac670af51f10b25194a097656a5ac13aebf

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    hand, trick, broken, first = x
    def penalty(c):
        return c[1] == 'H' or c == 'QS'
    if not trick:
        if first and '2C' in hand:
            return ['2C']
        if not broken:
            safe = [c for c in hand if c[1] != 'H']
            if safe:
                return safe
        return list(hand)
    suit = sorted(trick)[0][1]
    follow = [c for c in hand if c[1] == suit]
    if follow:
        return follow
    if first:
        clean = [c for c in hand if not penalty(c)]
        if clean:
            return clean
    return list(hand)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['4C', '8D', '9S', 'QC', 'TC', 'TS'], ['8S', 'QD', '7C'], True, False], ['9S', 'TS']], [[['5H', '6C', '7C', '9C', 'KD', 'QC', 'TS'], ['3S', '8S', '2S'], False, True], ['TS']], [[['2C', 'JS'], [], True, True], ['2C']], [[['AS', 'TH'], [], False, True], ['AS']], [[['2C', '8C', 'AS', 'KH'], [], True, False], ['2C', '8C', 'AS', 'KH']], [[['2H', '3C', '3H', 'QC'], [], True, False], ['2H', '3C', '3H', 'QC']], [[['7C'], ['4S'], False, True], ['7C']], [[['6H', '7H', '8H', 'JD', 'KC', 'KD', 'TS'], ['AC', '7D', '9S'], False, False], ['KC']]], [[[['2S', 'KS', 'TH'], ['JC', '2C'], False, True], ['2S', 'KS']], [[['2S'], ['QS'], False, False], ['2S']], [[['2H', '6C', '7H', '9C', '9S'], ['QC', '2C', '6H'], False, False], ['6C', '9C']], [[['3H', '5D', '9D'], ['3C'], True, False], ['3H', '5D', '9D']], [[['AH', 'JH', 'TH'], ['9C', 'TS'], True, False], ['AH', 'JH', 'TH']], [[['2H', '6S', '8D', 'AC', 'JD', 'QH'], ['4H', 'KC', '3S'], True, True], ['2H', 'QH']], [[['3D', '9H', 'QS'], [], False, False], ['3D', 'QS']], [[['4S', '5C', 'JC', 'KD', 'QD'], ['9H', '3C'], False, False], ['4S', '5C', 'JC', 'KD', 'QD']]], [[[['5C'], [], False, True], ['5C']], [[['5D', 'JH'], ['QH', 'KC', '8S'], True, False], ['JH']], [[['4H'], [], True, True], ['4H']], [[['3D', '5C'], [], True, False], ['3D', '5C']], [[['2H', '5H'], ['KS', '9C', '7H'], False, False], ['2H', '5H']], [[['8C', 'AC', 'KD', 'TS'], [], False, True], ['8C', 'AC', 'KD', 'TS']], [[['5D', 'AS'], ['5S', '3C'], False, False], ['AS']], [[['3H', '7C', '8S', '9S', 'JC', 'KC', 'KH', 'KS'], ['8H', '4C', '4D'], True, False], ['3H', 'KH']]], [[[['2H', '6C', '6D', '7C', 'KS', 'QC'], ['3C', '7D'], True, False], ['6C', '7C', 'QC']], [[['AC'], ['2C', '8D'], False, False], ['AC']], [[['2S', '6D'], [], True, False], ['2S', '6D']], [[['5D', 'AS'], ['5S', '3C'], False, False], ['AS']], [[['3C', '4S', '6S', '7D', '8H', 'AS', 'KC', 'KH'], [], False, False], ['3C', '4S', '6S', '7D', 'AS', 'KC']], [[['2H', '3D', '5H', '9D', 'JC', 'KS', 'QD'], [], False, False], ['3D', '9D', 'JC', 'KS', 'QD']], [[['2D', '3H', '3S', '4S', '6D', 'QC', 'QH'], ['6C', 'AH'], False, False], ['QC']], [[['6H', '6S', 'TC', 'TS'], ['6D', '4D', '2H'], False, False], ['6H', '6S', 'TC', 'TS']]], [[[['4C', '8D', '9S', 'QC', 'TC', 'TS'], ['8S', 'QD', '7C'], True, False], ['9S', 'TS']], [[['3S', '7D'], [], False, True], ['3S', '7D']], [[['2D', '3H', '3S', '4S', '6D', 'QC', 'QH'], ['6C', 'AH'], False, False], ['QC']], [[['3C', '6D', 'TH'], [], False, False], ['3C', '6D']], [[['3D', '5C', '6S', '7H', 'KH', 'TH', 'TS'], [], False, False], ['3D', '5C', '6S', 'TS']], [[['7D', '8H', 'AD', 'AS'], ['AH', '4H', '9C'], True, True], ['8H']], [[['2C', '4C', '6H', '8H', '9D', 'KC', 'TS'], ['6C', '9C'], False, False], ['2C', '4C', 'KC']], [[['2C', '3C', '5D', '6H', 'KD', 'QC', 'TC', 'TH'], ['8C', '6S'], True, True], ['2C', '3C', 'QC', 'TC']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("legal plays 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
legal plays case 0['4C', 'QC', 'TC']['9S', 'TS']Failed
legal plays case 1['TS']['TS']Passed
legal plays case 2['2C']['2C']Passed
legal plays case 3['AS']['AS']Passed
legal plays case 4['2C', '8C', 'AS', 'KH']['2C', '8C', 'AS', 'KH']Passed
legal plays case 5['2H', '3C', '3H', 'QC']['2H', '3C', '3H', 'QC']Passed
legal plays case 6['7C']['7C']Passed
legal plays case 7['JD', 'KD']['KC']Failed

SHA-256 / e80ca8b6f729f09f55c18673d6a0b7759991ac35caf4b9f27dcea31c008a2357

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    hand, trick, broken, first = x
    def penalty(c):
        return c[1] == 'H' or c == 'QS'
    if not trick:
        if first and '2C' in hand:
            return ['2C']
        if not broken:
            safe = [c for c in hand if c[1] != 'H']
            if safe:
                return safe
        return list(hand)
    suit = trick[0][1]
    follow = [c for c in hand if c[1] == suit]
    if follow:
        return follow
    if first:
        clean = [c for c in hand if not penalty(c)]
        if clean:
            return clean
    return list(hand)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['4C', '8D', '9S', 'QC', 'TC', 'TS'], ['8S', 'QD', '7C'], True, False], ['9S', 'TS']], [[['5H', '6C', '7C', '9C', 'KD', 'QC', 'TS'], ['3S', '8S', '2S'], False, True], ['TS']], [[['2C', 'JS'], [], True, True], ['2C']], [[['AS', 'TH'], [], False, True], ['AS']], [[['2C', '8C', 'AS', 'KH'], [], True, False], ['2C', '8C', 'AS', 'KH']], [[['2H', '3C', '3H', 'QC'], [], True, False], ['2H', '3C', '3H', 'QC']], [[['7C'], ['4S'], False, True], ['7C']], [[['6H', '7H', '8H', 'JD', 'KC', 'KD', 'TS'], ['AC', '7D', '9S'], False, False], ['KC']]], [[[['2S', 'KS', 'TH'], ['JC', '2C'], False, True], ['2S', 'KS']], [[['2S'], ['QS'], False, False], ['2S']], [[['2H', '6C', '7H', '9C', '9S'], ['QC', '2C', '6H'], False, False], ['6C', '9C']], [[['3H', '5D', '9D'], ['3C'], True, False], ['3H', '5D', '9D']], [[['AH', 'JH', 'TH'], ['9C', 'TS'], True, False], ['AH', 'JH', 'TH']], [[['2H', '6S', '8D', 'AC', 'JD', 'QH'], ['4H', 'KC', '3S'], True, True], ['2H', 'QH']], [[['3D', '9H', 'QS'], [], False, False], ['3D', 'QS']], [[['4S', '5C', 'JC', 'KD', 'QD'], ['9H', '3C'], False, False], ['4S', '5C', 'JC', 'KD', 'QD']]], [[[['5C'], [], False, True], ['5C']], [[['5D', 'JH'], ['QH', 'KC', '8S'], True, False], ['JH']], [[['4H'], [], True, True], ['4H']], [[['3D', '5C'], [], True, False], ['3D', '5C']], [[['2H', '5H'], ['KS', '9C', '7H'], False, False], ['2H', '5H']], [[['8C', 'AC', 'KD', 'TS'], [], False, True], ['8C', 'AC', 'KD', 'TS']], [[['5D', 'AS'], ['5S', '3C'], False, False], ['AS']], [[['3H', '7C', '8S', '9S', 'JC', 'KC', 'KH', 'KS'], ['8H', '4C', '4D'], True, False], ['3H', 'KH']]], [[[['2H', '6C', '6D', '7C', 'KS', 'QC'], ['3C', '7D'], True, False], ['6C', '7C', 'QC']], [[['AC'], ['2C', '8D'], False, False], ['AC']], [[['2S', '6D'], [], True, False], ['2S', '6D']], [[['5D', 'AS'], ['5S', '3C'], False, False], ['AS']], [[['3C', '4S', '6S', '7D', '8H', 'AS', 'KC', 'KH'], [], False, False], ['3C', '4S', '6S', '7D', 'AS', 'KC']], [[['2H', '3D', '5H', '9D', 'JC', 'KS', 'QD'], [], False, False], ['3D', '9D', 'JC', 'KS', 'QD']], [[['2D', '3H', '3S', '4S', '6D', 'QC', 'QH'], ['6C', 'AH'], False, False], ['QC']], [[['6H', '6S', 'TC', 'TS'], ['6D', '4D', '2H'], False, False], ['6H', '6S', 'TC', 'TS']]], [[[['4C', '8D', '9S', 'QC', 'TC', 'TS'], ['8S', 'QD', '7C'], True, False], ['9S', 'TS']], [[['3S', '7D'], [], False, True], ['3S', '7D']], [[['2D', '3H', '3S', '4S', '6D', 'QC', 'QH'], ['6C', 'AH'], False, False], ['QC']], [[['3C', '6D', 'TH'], [], False, False], ['3C', '6D']], [[['3D', '5C', '6S', '7H', 'KH', 'TH', 'TS'], [], False, False], ['3D', '5C', '6S', 'TS']], [[['7D', '8H', 'AD', 'AS'], ['AH', '4H', '9C'], True, True], ['8H']], [[['2C', '4C', '6H', '8H', '9D', 'KC', 'TS'], ['6C', '9C'], False, False], ['2C', '4C', 'KC']], [[['2C', '3C', '5D', '6H', 'KD', 'QC', 'TC', 'TH'], ['8C', '6S'], True, True], ['2C', '3C', 'QC', 'TC']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("legal plays 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
legal plays case 0['9S', 'TS']['9S', 'TS']Passed
legal plays case 1['TS']['TS']Passed
legal plays case 2['2C']['2C']Passed
legal plays case 3['AS']['AS']Passed
legal plays case 4['2C', '8C', 'AS', 'KH']['2C', '8C', 'AS', 'KH']Passed
legal plays case 5['2H', '3C', '3H', 'QC']['2H', '3C', '3H', 'QC']Passed
legal plays case 6['7C']['7C']Passed
legal plays case 7['KC']['KC']Passed

SHA-256 / 539f54bd6577db5b7a41aa022007fe3ef03a332b3f5a0aa385c571599b3f6b8d

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

Case digest / 3f7b826c2e430d52bf885c987ac8ddcac92e7a70176862f448f6f3b700803091