FA-83366 / Card game rule engines / Open access
Unbroken-hearts lead restriction also forbids the queen of spades · case 01
A player cannot lead QS before hearts are broken.
ROOT CAUSE
The lead filter reuses the penalty-card predicate.
VERIFIED REPAIR
Before hearts are broken only heart leads are restricted.
Unsuccessful approach: Returning the filtered list unconditionally leaves an all-heart hand with no legal lead.
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 not penalty(c)]
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 = [[[[['6H', 'AH', 'JC'], ['7S', 'KC'], False, False], ['6H', 'AH', 'JC']], [[['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']], [[['3D', '9H', 'QS'], [], False, False], ['3D', 'QS']], [[['3H'], [], False, False], ['3H']]], [[[['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']], [[['2H', '4C', 'AH', 'AS', 'KC', 'QS', 'TS'], [], False, False], ['4C', 'AS', 'KC', 'QS', 'TS']], [[['3H', '5H'], [], False, False], ['3H', '5H']]], [[[['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']], [[['4H', '6D', '7C', '7S', '9D', 'QS', 'TD'], [], False, True], ['6D', '7C', '7S', '9D', 'QS', 'TD']], [[['4H'], [], False, False], ['4H']]], [[[['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']], [[['7H'], [], False, False], ['7H']], [[['QS', 'AD'], [], False, False], ['QS', 'AD']]], [[[['4D', '6S', '9C', 'JD'], ['9H', 'AS', 'KC'], False, True], ['4D', '6S', '9C', 'JD']], [[['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']], [[['3C', '3S', '4H', 'KC', 'QS'], [], False, False], ['3C', '3S', 'KC', 'QS']], [[['3H'], [], False, False], ['3H']]]]
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 | ['6H', 'AH', 'JC'] | ['6H', 'AH', 'JC'] | 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 | ['3D'] | ['3D', 'QS'] | Failed |
| legal plays case 7 | ['3H'] | ['3H'] | Passed |
SHA-256 / c199894d5b9cf2ab23f6bfc98526324e571367c74764009fad914ac5329b9b8f
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']
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 = [[[[['6H', 'AH', 'JC'], ['7S', 'KC'], False, False], ['6H', 'AH', 'JC']], [[['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']], [[['3D', '9H', 'QS'], [], False, False], ['3D', 'QS']], [[['3H'], [], False, False], ['3H']]], [[[['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']], [[['2H', '4C', 'AH', 'AS', 'KC', 'QS', 'TS'], [], False, False], ['4C', 'AS', 'KC', 'QS', 'TS']], [[['3H', '5H'], [], False, False], ['3H', '5H']]], [[[['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']], [[['4H', '6D', '7C', '7S', '9D', 'QS', 'TD'], [], False, True], ['6D', '7C', '7S', '9D', 'QS', 'TD']], [[['4H'], [], False, False], ['4H']]], [[[['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']], [[['7H'], [], False, False], ['7H']], [[['QS', 'AD'], [], False, False], ['QS', 'AD']]], [[[['4D', '6S', '9C', 'JD'], ['9H', 'AS', 'KC'], False, True], ['4D', '6S', '9C', 'JD']], [[['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']], [[['3C', '3S', '4H', 'KC', 'QS'], [], False, False], ['3C', '3S', 'KC', 'QS']], [[['3H'], [], False, False], ['3H']]]]
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 | ['6H', 'AH', 'JC'] | ['6H', 'AH', 'JC'] | 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 | ['3D', 'QS'] | ['3D', 'QS'] | Passed |
| legal plays case 7 | [] | ['3H'] | Failed |
SHA-256 / 7efb9f560bf643e5d5e48f9647a3c060f6fa12a94adf3a32749c2a0d433c22d0
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 = [[[[['6H', 'AH', 'JC'], ['7S', 'KC'], False, False], ['6H', 'AH', 'JC']], [[['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']], [[['3D', '9H', 'QS'], [], False, False], ['3D', 'QS']], [[['3H'], [], False, False], ['3H']]], [[[['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']], [[['2H', '4C', 'AH', 'AS', 'KC', 'QS', 'TS'], [], False, False], ['4C', 'AS', 'KC', 'QS', 'TS']], [[['3H', '5H'], [], False, False], ['3H', '5H']]], [[[['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']], [[['4H', '6D', '7C', '7S', '9D', 'QS', 'TD'], [], False, True], ['6D', '7C', '7S', '9D', 'QS', 'TD']], [[['4H'], [], False, False], ['4H']]], [[[['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']], [[['7H'], [], False, False], ['7H']], [[['QS', 'AD'], [], False, False], ['QS', 'AD']]], [[[['4D', '6S', '9C', 'JD'], ['9H', 'AS', 'KC'], False, True], ['4D', '6S', '9C', 'JD']], [[['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']], [[['3C', '3S', '4H', 'KC', 'QS'], [], False, False], ['3C', '3S', 'KC', 'QS']], [[['3H'], [], False, False], ['3H']]]]
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 | ['6H', 'AH', 'JC'] | ['6H', 'AH', 'JC'] | 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 | ['3D', 'QS'] | ['3D', 'QS'] | Passed |
| legal plays case 7 | ['3H'] | ['3H'] | Passed |
SHA-256 / 9de8bba53bdbb9ee838a7242a2914ca7a3c7583a16fa7df2c14238baa32d87a6
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.832696+00:00.
Case digest / e564d4dd3f7a123a3b19146e1a1ebc969820993113b0ff71c7285f2b7cc02b18