FA-82136 / Poker hand evaluation / Open access
Unsuffixed non-pair counted as offsuit only · case 01
"AK" counts 12 combos instead of 16.
ROOT CAUSE
An unsuffixed class is stored bare and then falls into the offsuit branch of the counter.
VERIFIED REPAIR
Expand an unsuffixed non-pair into its suited and offsuit classes.
Unsuccessful approach: Expanding only to the suited class counts 4 combos.
Case contract
Input: a comma-separated hold'em range string. Tokens: pairs "77", "77+" (up to AA), "TT-77"; non-pairs "AKs"/"AKo"/"AK" (no suffix means both suited and offsuit), "A9s+" (kicker rises to one below the top card), "A9s-A6s" (inclusive). Whitespace around tokens is ignored. Duplicated classes count once. Return total combos: pair 6, suited 4, offsuit 12.
Why this case matters
Range combo counts drive equity, frequency and blocker arithmetic in solvers and trackers.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
R='23456789TJQKA'
classes=set()
def add(h, suf):
if h[0]==h[1]: classes.add(h)
elif suf: classes.add(h+suf)
else:
classes.add(h)
for tok in x.split(','):
tok=tok.strip()
if not tok: continue
plus=tok.endswith('+')
t=tok.rstrip('+')
if '-' in t:
a,b=t.split('-')
if a[0]==a[1]:
lo,hi=sorted([R.index(a[0]),R.index(b[0])])
for i in range(lo,hi+1): add(R[i]*2,'')
else:
lo,hi=sorted([R.index(a[1]),R.index(b[1])])
for i in range(lo,hi+1): add(a[0]+R[i],a[2:])
continue
if t[0]==t[1]:
start=R.index(t[0])
stop=len(R)-1 if plus else start
for i in range(start,stop+1): add(R[i]*2,'')
else:
top=R.index(t[0]); k=R.index(t[1])
stop=top-1 if plus else k
for i in range(k,stop+1): add(t[0]+R[i],t[2:])
total=0
for c in classes:
if c[0]==c[1]: total+=6
elif c.endswith('s'): total+=4
else: total+=12
return total
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
('overlap dedupe', 'AKs,AK', 16)],
[('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
('reversed dash pairs', '77-TT', 24), ('spaced tokens', 'QQ+, AKs', 22),
('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6)],
[('unsuffixed class', 'AK', 16), ('reversed dash pairs', '77-TT', 24),
('spaced tokens', 'QQ+, AKs', 22), ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6),
('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4)],
[('pairs plus', '77+', 48), ('unsuffixed class', 'AK', 16), ('overlap dedupe', 'AKs,AK', 16),
('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4),
('trailing comma', 'KK+,', 12), ('reversed kicker dash', 'K6o-K9o', 48)],
[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('overlap dedupe', 'AKs,AK', 16), ('trailing comma', 'KK+,', 12),
('reversed kicker dash', 'K6o-K9o', 48)]]
for label, args, expected in fixtures[N-1]:
try:
actual = solve(args)
except Exception as exc:
actual = 'error:' + type(exc).__name__
check(label, actual, 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 |
|---|---|---|---|
| pairs plus | 48 | 48 | Passed |
| queens plus | 18 | 18 | Passed |
| suited ace plus | 20 | 20 | Passed |
| offsuit king plus | 36 | 36 | Passed |
| unsuffixed class | 12 | 16 | Failed |
| dash kicker range | 16 | 16 | Passed |
| dash pair range | 24 | 24 | Passed |
| overlap dedupe | 16 | 16 | Passed |
SHA-256 / 5c20c20ae8838aaa89a70c82be498a65bd6964b9e99f07a64cd7b03eb1acebe9
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
R='23456789TJQKA'
classes=set()
def add(h, suf):
if h[0]==h[1]: classes.add(h)
elif suf: classes.add(h+suf)
else:
classes.add(h+'s')
for tok in x.split(','):
tok=tok.strip()
if not tok: continue
plus=tok.endswith('+')
t=tok.rstrip('+')
if '-' in t:
a,b=t.split('-')
if a[0]==a[1]:
lo,hi=sorted([R.index(a[0]),R.index(b[0])])
for i in range(lo,hi+1): add(R[i]*2,'')
else:
lo,hi=sorted([R.index(a[1]),R.index(b[1])])
for i in range(lo,hi+1): add(a[0]+R[i],a[2:])
continue
if t[0]==t[1]:
start=R.index(t[0])
stop=len(R)-1 if plus else start
for i in range(start,stop+1): add(R[i]*2,'')
else:
top=R.index(t[0]); k=R.index(t[1])
stop=top-1 if plus else k
for i in range(k,stop+1): add(t[0]+R[i],t[2:])
total=0
for c in classes:
if c[0]==c[1]: total+=6
elif c.endswith('s'): total+=4
else: total+=12
return total
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
('overlap dedupe', 'AKs,AK', 16)],
[('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
('reversed dash pairs', '77-TT', 24), ('spaced tokens', 'QQ+, AKs', 22),
('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6)],
[('unsuffixed class', 'AK', 16), ('reversed dash pairs', '77-TT', 24),
('spaced tokens', 'QQ+, AKs', 22), ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6),
('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4)],
[('pairs plus', '77+', 48), ('unsuffixed class', 'AK', 16), ('overlap dedupe', 'AKs,AK', 16),
('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4),
('trailing comma', 'KK+,', 12), ('reversed kicker dash', 'K6o-K9o', 48)],
[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('overlap dedupe', 'AKs,AK', 16), ('trailing comma', 'KK+,', 12),
('reversed kicker dash', 'K6o-K9o', 48)]]
for label, args, expected in fixtures[N-1]:
try:
actual = solve(args)
except Exception as exc:
actual = 'error:' + type(exc).__name__
check(label, actual, 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 |
|---|---|---|---|
| pairs plus | 48 | 48 | Passed |
| queens plus | 18 | 18 | Passed |
| suited ace plus | 20 | 20 | Passed |
| offsuit king plus | 36 | 36 | Passed |
| unsuffixed class | 4 | 16 | Failed |
| dash kicker range | 16 | 16 | Passed |
| dash pair range | 24 | 24 | Passed |
| overlap dedupe | 4 | 16 | Failed |
SHA-256 / c5936576ced1e0bc02afdf52f38cb588b2511173fa306d73f099d1544cde613b
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
R='23456789TJQKA'
classes=set()
def add(h, suf):
if h[0]==h[1]: classes.add(h)
elif suf: classes.add(h+suf)
else:
classes.add(h+'s'); classes.add(h+'o')
for tok in x.split(','):
tok=tok.strip()
if not tok: continue
plus=tok.endswith('+')
t=tok.rstrip('+')
if '-' in t:
a,b=t.split('-')
if a[0]==a[1]:
lo,hi=sorted([R.index(a[0]),R.index(b[0])])
for i in range(lo,hi+1): add(R[i]*2,'')
else:
lo,hi=sorted([R.index(a[1]),R.index(b[1])])
for i in range(lo,hi+1): add(a[0]+R[i],a[2:])
continue
if t[0]==t[1]:
start=R.index(t[0])
stop=len(R)-1 if plus else start
for i in range(start,stop+1): add(R[i]*2,'')
else:
top=R.index(t[0]); k=R.index(t[1])
stop=top-1 if plus else k
for i in range(k,stop+1): add(t[0]+R[i],t[2:])
total=0
for c in classes:
if c[0]==c[1]: total+=6
elif c.endswith('s'): total+=4
else: total+=12
return total
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
('overlap dedupe', 'AKs,AK', 16)],
[('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
('reversed dash pairs', '77-TT', 24), ('spaced tokens', 'QQ+, AKs', 22),
('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6)],
[('unsuffixed class', 'AK', 16), ('reversed dash pairs', '77-TT', 24),
('spaced tokens', 'QQ+, AKs', 22), ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6),
('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4)],
[('pairs plus', '77+', 48), ('unsuffixed class', 'AK', 16), ('overlap dedupe', 'AKs,AK', 16),
('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4),
('trailing comma', 'KK+,', 12), ('reversed kicker dash', 'K6o-K9o', 48)],
[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
('overlap dedupe', 'AKs,AK', 16), ('trailing comma', 'KK+,', 12),
('reversed kicker dash', 'K6o-K9o', 48)]]
for label, args, expected in fixtures[N-1]:
try:
actual = solve(args)
except Exception as exc:
actual = 'error:' + type(exc).__name__
check(label, actual, 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 |
|---|---|---|---|
| pairs plus | 48 | 48 | Passed |
| queens plus | 18 | 18 | Passed |
| suited ace plus | 20 | 20 | Passed |
| offsuit king plus | 36 | 36 | Passed |
| unsuffixed class | 16 | 16 | Passed |
| dash kicker range | 16 | 16 | Passed |
| dash pair range | 24 | 24 | Passed |
| overlap dedupe | 16 | 16 | Passed |
SHA-256 / 29ea6c51d36e4c5030a96832fc4ca510d02c599e088240fc00f9643389014d1d
Verification & scope
A deterministic toy model with an explicitly stated contract; card strings are rank+suit (for example "As", "Td"); no claim of conformance to any casino or tournament rulebook. 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:09.466535+00:00.
Case digest / bf9a935cc18985611568af6b51fade548f88ce2e2dacfc2133453124c5d9ac0a