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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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