FAILURE MAP
← Case archive

FA-83421 / Card game rule engines / Open access

Twenty-one against twenty-one is paid as a win · case 01

Non-natural 21 ties are paid instead of pushed.

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

ROOT CAUSE

The final tie branch pays when both totals are 21.

VERIFIED REPAIR

Any remaining equal totals push.

Unsuccessful approach: Treating all ties as losses charges pushes.

Case contract

Input [player_cards, dealer_cards, bet, doubled, from_split]. A natural is a two-card 21 not from a split. Player bust loses the stake (2*bet if doubled) first; both naturals push; player natural pays 3:2 on the bet; dealer natural takes only the original bet; dealer bust pays the stake; otherwise compare totals, equal totals push. Return net units.

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):
    p, d, bet, doubled, split = x
    def total(h):
        t = 0
        a = 0
        for c in h:
            if c[0] == 'A':
                a += 1
                t += 1
            elif c[0] in 'TJQK':
                t += 10
            else:
                t += int(c[0])
        if a and t + 10 <= 21:
            t += 10
        return t
    stake = bet * 2 if doubled else bet
    pt = total(p)
    dt = total(d)
    pn = pt == 21 and len(p) == 2 and not split
    dn = dt == 21 and len(d) == 2
    if pt > 21:
        return -stake
    if pn and dn:
        return 0
    if pn:
        return bet * 3 // 2
    if dn:
        return -bet
    if dt > 21:
        return stake
    if pt > dt:
        return stake
    if pt < dt:
        return -stake
    return stake if pt == 21 else 0
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['7D', '8C'], ['JC', '7S', 'QD'], 4, False, True], 4], [[['KH', '5D'], ['AD', 'JD'], 4, True, False], -4], [[['9H', '2H', '2D'], ['KC', '3C'], 10, False, False], 0], [[['7S', '6D'], ['4H', 'AC'], 20, False, False], -20], [[['6D', 'JC'], ['9H', '6C'], 4, True, False], 8], [[['7H', 'JS'], ['8S', '2D'], 20, True, False], 40], [[['KS', '6D', 'AD'], ['3C', '4D', 'TH'], 4, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['2S', '6D', 'JS'], ['7H', 'AC', 'TD'], 4, False, False], 0], [[['3C', 'TH'], ['AC', '8C'], 4, False, True], -4], [[['9C', '2D', '5D', '8D'], ['6H', 'KH'], 20, False, False], -20], [[['2D', '6S'], ['6H', 'TC'], 4, False, False], -4], [[['4H', '7S', '2C'], ['JC', '4S'], 20, False, False], -20], [[['9C', '8S'], ['9S', '5H'], 4, False, False], 4], [[['8D', 'TH', '5H'], ['KD', '6D', '7D'], 20, False, True], -20], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['QS', 'QD', '5S'], ['8D', 'QH'], 4, False, False], -4], [[['4D', 'KS'], ['9D', 'JH', '9C'], 20, False, True], 20], [[['5H', '4D'], ['QH', '6D'], 2, False, False], -2], [[['5D', 'TC', '2C', '5S'], ['KD', 'QD'], 10, False, False], -10], [[['TH', '9S', '2H', '3H'], ['AS', 'QD'], 2, False, False], -2], [[['2D', '7C'], ['AC', 'TC'], 2, True, False], -2], [[['AS', '8D', '7S'], ['KS', '6C'], 20, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['3D', '3S', '3C', 'JC'], ['TC', '9D'], 20, False, False], 0], [[['QS', '5S'], ['7S', '9C', '9S'], 2, False, True], 2], [[['4C', 'KH'], ['7H', 'TS'], 10, True, False], -20], [[['KH', '4D'], ['QD', '7S'], 2, False, False], -2], [[['TH', '3S', '5C', '8D'], ['8S', '6S'], 4, False, False], -4], [[['JC', '7S'], ['9D', 'JH', '6C'], 20, False, False], 20], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['6D', '7C'], ['JD', '8S'], 2, True, False], -4], [[['7H', 'QH'], ['7S', '4C'], 10, False, False], 10], [[['AC', '7D'], ['4D', '4C'], 20, False, False], 20], [[['AS', '3C'], ['QD', '9C'], 4, False, False], -4], [[['KS', '8H', 'JD'], ['TS', 'AS'], 4, False, True], -4], [[['TD', 'QS', 'TH'], ['8H', 'TS'], 20, False, False], -20], [[['JH', '7D', 'AS'], ['TD', '8S'], 20, True, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("settlement 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
settlement case 044Passed
settlement case 1-4-4Passed
settlement case 200Passed
settlement case 3-20-20Passed
settlement case 488Passed
settlement case 54040Passed
settlement case 600Passed
settlement case 7100Failed

SHA-256 / 0e7f5ad63b0b43737d3302363d719ff9ac1ec23d9aea57ecec3a7e7858cd7e73

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    p, d, bet, doubled, split = x
    def total(h):
        t = 0
        a = 0
        for c in h:
            if c[0] == 'A':
                a += 1
                t += 1
            elif c[0] in 'TJQK':
                t += 10
            else:
                t += int(c[0])
        if a and t + 10 <= 21:
            t += 10
        return t
    stake = bet * 2 if doubled else bet
    pt = total(p)
    dt = total(d)
    pn = pt == 21 and len(p) == 2 and not split
    dn = dt == 21 and len(d) == 2
    if pt > 21:
        return -stake
    if pn and dn:
        return 0
    if pn:
        return bet * 3 // 2
    if dn:
        return -bet
    if dt > 21:
        return stake
    if pt > dt:
        return stake
    return -stake
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['7D', '8C'], ['JC', '7S', 'QD'], 4, False, True], 4], [[['KH', '5D'], ['AD', 'JD'], 4, True, False], -4], [[['9H', '2H', '2D'], ['KC', '3C'], 10, False, False], 0], [[['7S', '6D'], ['4H', 'AC'], 20, False, False], -20], [[['6D', 'JC'], ['9H', '6C'], 4, True, False], 8], [[['7H', 'JS'], ['8S', '2D'], 20, True, False], 40], [[['KS', '6D', 'AD'], ['3C', '4D', 'TH'], 4, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['2S', '6D', 'JS'], ['7H', 'AC', 'TD'], 4, False, False], 0], [[['3C', 'TH'], ['AC', '8C'], 4, False, True], -4], [[['9C', '2D', '5D', '8D'], ['6H', 'KH'], 20, False, False], -20], [[['2D', '6S'], ['6H', 'TC'], 4, False, False], -4], [[['4H', '7S', '2C'], ['JC', '4S'], 20, False, False], -20], [[['9C', '8S'], ['9S', '5H'], 4, False, False], 4], [[['8D', 'TH', '5H'], ['KD', '6D', '7D'], 20, False, True], -20], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['QS', 'QD', '5S'], ['8D', 'QH'], 4, False, False], -4], [[['4D', 'KS'], ['9D', 'JH', '9C'], 20, False, True], 20], [[['5H', '4D'], ['QH', '6D'], 2, False, False], -2], [[['5D', 'TC', '2C', '5S'], ['KD', 'QD'], 10, False, False], -10], [[['TH', '9S', '2H', '3H'], ['AS', 'QD'], 2, False, False], -2], [[['2D', '7C'], ['AC', 'TC'], 2, True, False], -2], [[['AS', '8D', '7S'], ['KS', '6C'], 20, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['3D', '3S', '3C', 'JC'], ['TC', '9D'], 20, False, False], 0], [[['QS', '5S'], ['7S', '9C', '9S'], 2, False, True], 2], [[['4C', 'KH'], ['7H', 'TS'], 10, True, False], -20], [[['KH', '4D'], ['QD', '7S'], 2, False, False], -2], [[['TH', '3S', '5C', '8D'], ['8S', '6S'], 4, False, False], -4], [[['JC', '7S'], ['9D', 'JH', '6C'], 20, False, False], 20], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['6D', '7C'], ['JD', '8S'], 2, True, False], -4], [[['7H', 'QH'], ['7S', '4C'], 10, False, False], 10], [[['AC', '7D'], ['4D', '4C'], 20, False, False], 20], [[['AS', '3C'], ['QD', '9C'], 4, False, False], -4], [[['KS', '8H', 'JD'], ['TS', 'AS'], 4, False, True], -4], [[['TD', 'QS', 'TH'], ['8H', 'TS'], 20, False, False], -20], [[['JH', '7D', 'AS'], ['TD', '8S'], 20, True, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("settlement 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
settlement case 044Passed
settlement case 1-4-4Passed
settlement case 2-100Failed
settlement case 3-20-20Passed
settlement case 488Passed
settlement case 54040Passed
settlement case 6-40Failed
settlement case 7-100Failed

SHA-256 / c6e46a2e02acf312bdda1ce7b93eecf50388fb973f484ca445e559435026630a

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    p, d, bet, doubled, split = x
    def total(h):
        t = 0
        a = 0
        for c in h:
            if c[0] == 'A':
                a += 1
                t += 1
            elif c[0] in 'TJQK':
                t += 10
            else:
                t += int(c[0])
        if a and t + 10 <= 21:
            t += 10
        return t
    stake = bet * 2 if doubled else bet
    pt = total(p)
    dt = total(d)
    pn = pt == 21 and len(p) == 2 and not split
    dn = dt == 21 and len(d) == 2
    if pt > 21:
        return -stake
    if pn and dn:
        return 0
    if pn:
        return bet * 3 // 2
    if dn:
        return -bet
    if dt > 21:
        return stake
    if pt > dt:
        return stake
    if pt < dt:
        return -stake
    return 0
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['7D', '8C'], ['JC', '7S', 'QD'], 4, False, True], 4], [[['KH', '5D'], ['AD', 'JD'], 4, True, False], -4], [[['9H', '2H', '2D'], ['KC', '3C'], 10, False, False], 0], [[['7S', '6D'], ['4H', 'AC'], 20, False, False], -20], [[['6D', 'JC'], ['9H', '6C'], 4, True, False], 8], [[['7H', 'JS'], ['8S', '2D'], 20, True, False], 40], [[['KS', '6D', 'AD'], ['3C', '4D', 'TH'], 4, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['2S', '6D', 'JS'], ['7H', 'AC', 'TD'], 4, False, False], 0], [[['3C', 'TH'], ['AC', '8C'], 4, False, True], -4], [[['9C', '2D', '5D', '8D'], ['6H', 'KH'], 20, False, False], -20], [[['2D', '6S'], ['6H', 'TC'], 4, False, False], -4], [[['4H', '7S', '2C'], ['JC', '4S'], 20, False, False], -20], [[['9C', '8S'], ['9S', '5H'], 4, False, False], 4], [[['8D', 'TH', '5H'], ['KD', '6D', '7D'], 20, False, True], -20], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['QS', 'QD', '5S'], ['8D', 'QH'], 4, False, False], -4], [[['4D', 'KS'], ['9D', 'JH', '9C'], 20, False, True], 20], [[['5H', '4D'], ['QH', '6D'], 2, False, False], -2], [[['5D', 'TC', '2C', '5S'], ['KD', 'QD'], 10, False, False], -10], [[['TH', '9S', '2H', '3H'], ['AS', 'QD'], 2, False, False], -2], [[['2D', '7C'], ['AC', 'TC'], 2, True, False], -2], [[['AS', '8D', '7S'], ['KS', '6C'], 20, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['3D', '3S', '3C', 'JC'], ['TC', '9D'], 20, False, False], 0], [[['QS', '5S'], ['7S', '9C', '9S'], 2, False, True], 2], [[['4C', 'KH'], ['7H', 'TS'], 10, True, False], -20], [[['KH', '4D'], ['QD', '7S'], 2, False, False], -2], [[['TH', '3S', '5C', '8D'], ['8S', '6S'], 4, False, False], -4], [[['JC', '7S'], ['9D', 'JH', '6C'], 20, False, False], 20], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]], [[[['6D', '7C'], ['JD', '8S'], 2, True, False], -4], [[['7H', 'QH'], ['7S', '4C'], 10, False, False], 10], [[['AC', '7D'], ['4D', '4C'], 20, False, False], 20], [[['AS', '3C'], ['QD', '9C'], 4, False, False], -4], [[['KS', '8H', 'JD'], ['TS', 'AS'], 4, False, True], -4], [[['TD', 'QS', 'TH'], ['8H', 'TS'], 20, False, False], -20], [[['JH', '7D', 'AS'], ['TD', '8S'], 20, True, False], 0], [[['5S', '6H', 'TD'], ['9C', '5D', '7H'], 10, False, False], 0]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("settlement 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
settlement case 044Passed
settlement case 1-4-4Passed
settlement case 200Passed
settlement case 3-20-20Passed
settlement case 488Passed
settlement case 54040Passed
settlement case 600Passed
settlement case 700Passed

SHA-256 / 7bf504cc045d2988410e20c0eb3cd254b7090dbf422409958fa4c921d8a4fda5

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

Case digest / e6a98cab22ed09f16c802a42d79bc6a7503e4ad00b816354b2416560e66b465f