FAILURE MAP
← Case archive

FA-83406 / Card game rule engines / Open access

Ace-ten after a split is paid as a natural · case 01

Split hands reaching 21 in two cards collect 3:2.

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

ROOT CAUSE

The natural test ignores the split flag.

VERIFIED REPAIR

A natural requires two cards and a hand that did not come from a split.

Unsuccessful approach: Dropping the two-card requirement pays 3:2 on multi-card 21s.

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
    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], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4], [[['7S', '4H', 'TC'], ['TC', 'AD'], 10, True, False], -10]], [[[['JD', 'QC'], ['JC', '2D'], 20, False, False], 20], [[['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], [[['KS', '3D', '4D', '4H'], ['5H', 'QC'], 20, False, False], 20], [[['AS', 'KH'], ['9C', '8D'], 10, False, True], 10]], [[[['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], [[['TD', '9C', '2D'], ['AH', '8D'], 4, True, False], 8], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4]], [[[['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], [[['8D', 'AC', '7C', '5C'], ['9C', '8H'], 20, False, False], 20], [[['AS', 'KH'], ['9C', '8D'], 10, False, True], 10]], [[[['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], [[['8S', 'TS', '3S'], ['TD', '9H', 'AS'], 10, False, False], 10], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4]]]
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 664Failed
settlement case 7-10-10Passed

SHA-256 / 3e3b0501daf18142029be480d9f066896cabca38817386be1599dfbe90ee529a

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 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], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4], [[['7S', '4H', 'TC'], ['TC', 'AD'], 10, True, False], -10]], [[[['JD', 'QC'], ['JC', '2D'], 20, False, False], 20], [[['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], [[['KS', '3D', '4D', '4H'], ['5H', 'QC'], 20, False, False], 20], [[['AS', 'KH'], ['9C', '8D'], 10, False, True], 10]], [[[['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], [[['TD', '9C', '2D'], ['AH', '8D'], 4, True, False], 8], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4]], [[[['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], [[['8D', 'AC', '7C', '5C'], ['9C', '8H'], 20, False, False], 20], [[['AS', 'KH'], ['9C', '8D'], 10, False, True], 10]], [[[['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], [[['8S', 'TS', '3S'], ['TD', '9H', 'AS'], 10, False, False], 10], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4]]]
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 644Passed
settlement case 70-10Failed

SHA-256 / 8cf99244fd841e193b7b371774501bf0f15bdd490eef73e02e63724e8b515141

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], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4], [[['7S', '4H', 'TC'], ['TC', 'AD'], 10, True, False], -10]], [[[['JD', 'QC'], ['JC', '2D'], 20, False, False], 20], [[['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], [[['KS', '3D', '4D', '4H'], ['5H', 'QC'], 20, False, False], 20], [[['AS', 'KH'], ['9C', '8D'], 10, False, True], 10]], [[[['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], [[['TD', '9C', '2D'], ['AH', '8D'], 4, True, False], 8], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4]], [[[['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], [[['8D', 'AC', '7C', '5C'], ['9C', '8H'], 20, False, False], 20], [[['AS', 'KH'], ['9C', '8D'], 10, False, True], 10]], [[[['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], [[['8S', 'TS', '3S'], ['TD', '9H', 'AS'], 10, False, False], 10], [[['TD', 'AD'], ['JC', '7H'], 4, False, True], 4]]]
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 644Passed
settlement case 7-10-10Passed

SHA-256 / 8f66cccecbfc0ba8a05c8c002a155db8b94e541fb2c564a5f70a322e6eb72b0f

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

Case digest / 3f9479db92bd9032a9b463959567c9de45fd8c24fb57c6fe9c3b463e3d98cdd4