FAILURE MAP
← Case archive

FA-83611 / Card game rule engines / Open access

Vulnerable undoubled undertricks cost 50 · case 01

Vulnerable declarers lose half the penalty.

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

ROOT CAUSE

The undoubled penalty ignores vulnerability.

VERIFIED REPAIR

Undoubled undertricks cost 100 each vulnerable.

Unsuccessful approach: Charging 100 only for the first vulnerable undertrick undercharges the rest.

Case contract

Input [level, strain, doubled(0/1/2), vulnerable, tricks]. Trick score C/D 20, H/S 30, NT 40+30; x2/x4 doubled/redoubled. Game bonus 300/500 when trick score >= 100 else 50 partscore; slam 500/750 (6) or 1000/1500 (7); overtricks trick value undoubled, 100/200 per double level; insult 50 per double level. Undertricks: 50/100 undoubled; doubled NV 100, 200, 200, then 300; doubled V 200 then 300; redoubled x2.

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):
    level, strain, dbl, vul, tricks = x
    need = 6 + level
    if tricks < need:
        down = need - tricks
        if dbl == 0:
            return -50 * down
        pen = 0
        for k in range(1, down + 1):
            if vul:
                pen += 200 if k == 1 else 300
            else:
                pen += 100 if k == 1 else 200 if k <= 3 else 300
        return -pen * dbl
    per = 20 if strain in 'CD' else 30
    base = per * level + (10 if strain == 'N' else 0)
    mult = [1, 2, 4][dbl]
    trick_score = base * mult
    score = trick_score
    score += (500 if vul else 300) if trick_score >= 100 else 50
    if level == 6:
        score += 750 if vul else 500
    elif level == 7:
        score += 1500 if vul else 1000
    over = tricks - need
    if dbl == 0:
        score += over * per
    else:
        score += over * (200 if vul else 100) * dbl
    score += 50 * dbl
    return score
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[7, 'D', 0, True, 11], -200], [[7, 'S', 0, True, 12], -100], [[6, 'N', 0, True, 12], 1440], [[2, 'H', 1, True, 9], 870], [[3, 'H', 2, False, 11], 1160], [[5, 'N', 0, False, 11], 460], [[2, 'D', 0, True, 8], 90], [[6, 'H', 0, True, 10], -200]], [[[7, 'S', 0, True, 12], -100], [[7, 'D', 0, True, 8], -500], [[6, 'H', 0, False, 12], 980], [[6, 'H', 0, False, 11], -50], [[6, 'N', 0, False, 12], 990], [[6, 'D', 2, True, 13], 2230], [[5, 'N', 0, True, 6], -500], [[5, 'S', 0, True, 8], -300]], [[[6, 'H', 0, True, 13], 1460], [[2, 'C', 0, True, 9], 110], [[4, 'N', 0, False, 12], 490], [[7, 'C', 0, True, 13], 2140], [[3, 'S', 1, False, 5], -800], [[5, 'C', 2, False, 13], 1200], [[4, 'D', 0, True, 7], -300], [[2, 'D', 0, True, 3], -500]], [[[4, 'N', 2, True, 11], 1520], [[2, 'C', 2, False, 7], -200], [[2, 'H', 2, False, 5], -1000], [[7, 'H', 2, False, 11], -600], [[3, 'N', 1, True, 7], -500], [[6, 'S', 0, True, 12], 1430], [[7, 'C', 0, True, 12], -100], [[5, 'N', 0, True, 6], -500]], [[[1, 'D', 2, True, 6], -400], [[5, 'H', 0, True, 11], 650], [[4, 'D', 0, True, 7], -300], [[7, 'D', 1, True, 10], -800], [[4, 'C', 1, True, 9], -200], [[3, 'S', 2, True, 9], 960], [[2, 'D', 0, True, 5], -300], [[5, 'H', 0, True, 8], -300]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("duplicate score 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
duplicate score case 0-100-200Failed
duplicate score case 1-50-100Failed
duplicate score case 214401440Passed
duplicate score case 3870870Passed
duplicate score case 411601160Passed
duplicate score case 5460460Passed
duplicate score case 69090Passed
duplicate score case 7-100-200Failed

SHA-256 / 006d20d9eb34481497338d35e11df2e6191b668a4a42f2e431de4cc71b59a7e9

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    level, strain, dbl, vul, tricks = x
    need = 6 + level
    if tricks < need:
        down = need - tricks
        if dbl == 0:
            return -(100 if vul else 50) - 50 * (down - 1)
        pen = 0
        for k in range(1, down + 1):
            if vul:
                pen += 200 if k == 1 else 300
            else:
                pen += 100 if k == 1 else 200 if k <= 3 else 300
        return -pen * dbl
    per = 20 if strain in 'CD' else 30
    base = per * level + (10 if strain == 'N' else 0)
    mult = [1, 2, 4][dbl]
    trick_score = base * mult
    score = trick_score
    score += (500 if vul else 300) if trick_score >= 100 else 50
    if level == 6:
        score += 750 if vul else 500
    elif level == 7:
        score += 1500 if vul else 1000
    over = tricks - need
    if dbl == 0:
        score += over * per
    else:
        score += over * (200 if vul else 100) * dbl
    score += 50 * dbl
    return score
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[7, 'D', 0, True, 11], -200], [[7, 'S', 0, True, 12], -100], [[6, 'N', 0, True, 12], 1440], [[2, 'H', 1, True, 9], 870], [[3, 'H', 2, False, 11], 1160], [[5, 'N', 0, False, 11], 460], [[2, 'D', 0, True, 8], 90], [[6, 'H', 0, True, 10], -200]], [[[7, 'S', 0, True, 12], -100], [[7, 'D', 0, True, 8], -500], [[6, 'H', 0, False, 12], 980], [[6, 'H', 0, False, 11], -50], [[6, 'N', 0, False, 12], 990], [[6, 'D', 2, True, 13], 2230], [[5, 'N', 0, True, 6], -500], [[5, 'S', 0, True, 8], -300]], [[[6, 'H', 0, True, 13], 1460], [[2, 'C', 0, True, 9], 110], [[4, 'N', 0, False, 12], 490], [[7, 'C', 0, True, 13], 2140], [[3, 'S', 1, False, 5], -800], [[5, 'C', 2, False, 13], 1200], [[4, 'D', 0, True, 7], -300], [[2, 'D', 0, True, 3], -500]], [[[4, 'N', 2, True, 11], 1520], [[2, 'C', 2, False, 7], -200], [[2, 'H', 2, False, 5], -1000], [[7, 'H', 2, False, 11], -600], [[3, 'N', 1, True, 7], -500], [[6, 'S', 0, True, 12], 1430], [[7, 'C', 0, True, 12], -100], [[5, 'N', 0, True, 6], -500]], [[[1, 'D', 2, True, 6], -400], [[5, 'H', 0, True, 11], 650], [[4, 'D', 0, True, 7], -300], [[7, 'D', 1, True, 10], -800], [[4, 'C', 1, True, 9], -200], [[3, 'S', 2, True, 9], 960], [[2, 'D', 0, True, 5], -300], [[5, 'H', 0, True, 8], -300]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("duplicate score 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
duplicate score case 0-150-200Failed
duplicate score case 1-100-100Passed
duplicate score case 214401440Passed
duplicate score case 3870870Passed
duplicate score case 411601160Passed
duplicate score case 5460460Passed
duplicate score case 69090Passed
duplicate score case 7-150-200Failed

SHA-256 / 9b2a285e5f9738ced2058ce8e9a79f2bf6cce61070cd0e71ee51d08fe73a5162

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    level, strain, dbl, vul, tricks = x
    need = 6 + level
    if tricks < need:
        down = need - tricks
        if dbl == 0:
            return -(100 if vul else 50) * down
        pen = 0
        for k in range(1, down + 1):
            if vul:
                pen += 200 if k == 1 else 300
            else:
                pen += 100 if k == 1 else 200 if k <= 3 else 300
        return -pen * dbl
    per = 20 if strain in 'CD' else 30
    base = per * level + (10 if strain == 'N' else 0)
    mult = [1, 2, 4][dbl]
    trick_score = base * mult
    score = trick_score
    score += (500 if vul else 300) if trick_score >= 100 else 50
    if level == 6:
        score += 750 if vul else 500
    elif level == 7:
        score += 1500 if vul else 1000
    over = tricks - need
    if dbl == 0:
        score += over * per
    else:
        score += over * (200 if vul else 100) * dbl
    score += 50 * dbl
    return score
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[7, 'D', 0, True, 11], -200], [[7, 'S', 0, True, 12], -100], [[6, 'N', 0, True, 12], 1440], [[2, 'H', 1, True, 9], 870], [[3, 'H', 2, False, 11], 1160], [[5, 'N', 0, False, 11], 460], [[2, 'D', 0, True, 8], 90], [[6, 'H', 0, True, 10], -200]], [[[7, 'S', 0, True, 12], -100], [[7, 'D', 0, True, 8], -500], [[6, 'H', 0, False, 12], 980], [[6, 'H', 0, False, 11], -50], [[6, 'N', 0, False, 12], 990], [[6, 'D', 2, True, 13], 2230], [[5, 'N', 0, True, 6], -500], [[5, 'S', 0, True, 8], -300]], [[[6, 'H', 0, True, 13], 1460], [[2, 'C', 0, True, 9], 110], [[4, 'N', 0, False, 12], 490], [[7, 'C', 0, True, 13], 2140], [[3, 'S', 1, False, 5], -800], [[5, 'C', 2, False, 13], 1200], [[4, 'D', 0, True, 7], -300], [[2, 'D', 0, True, 3], -500]], [[[4, 'N', 2, True, 11], 1520], [[2, 'C', 2, False, 7], -200], [[2, 'H', 2, False, 5], -1000], [[7, 'H', 2, False, 11], -600], [[3, 'N', 1, True, 7], -500], [[6, 'S', 0, True, 12], 1430], [[7, 'C', 0, True, 12], -100], [[5, 'N', 0, True, 6], -500]], [[[1, 'D', 2, True, 6], -400], [[5, 'H', 0, True, 11], 650], [[4, 'D', 0, True, 7], -300], [[7, 'D', 1, True, 10], -800], [[4, 'C', 1, True, 9], -200], [[3, 'S', 2, True, 9], 960], [[2, 'D', 0, True, 5], -300], [[5, 'H', 0, True, 8], -300]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("duplicate score 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
duplicate score case 0-200-200Passed
duplicate score case 1-100-100Passed
duplicate score case 214401440Passed
duplicate score case 3870870Passed
duplicate score case 411601160Passed
duplicate score case 5460460Passed
duplicate score case 69090Passed
duplicate score case 7-200-200Passed

SHA-256 / 775d9982a4b146ecb9946143642b987395b38d20475d4ed85a1ca732432f4824

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

Case digest / a7647ca21df574f40a7984de5f8dcf251989fcf189e9fee8ef8a1899fb5cb932