FAILURE MAP
← Case archive

FA-83011 / Go territory scoring / Open access

Half-point margins are truncated to whole points · case 01

W+0.5 is stored as a zero margin win.

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

ROOT CAUSE

The fractional digit is parsed but not added to the tenths value.

VERIFIED REPAIR

Return whole*10 + fraction digit.

Unsuccessful approach: Assuming every fractional margin is a half point turns W+3.0 into 3.5 and B+2.3 into 2.5.

Case contract

Input an SGF RE string. "0", "Draw", "Jigo" -> ["draw", 0, "score"]; "Void" -> ["void", None, "none"]; C+R/Resign, C+T/Time, C+F/Forfeit (case-insensitive) -> [C, None, reason]; "C+" -> [C, None, "unknown"]; C+digits with optional one-digit fraction -> [C, tenths, "score"]; else unknown.

Why this case matters

Go servers and scoring tools compute this value automatically; a wrong answer changes a game result.

1 / The failure

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

N = 1
observations = []
def solve(x):
    s = x.strip()
    if s in ('0', 'Draw', 'Jigo'):
        return ['draw', 0, 'score']
    if s == 'Void':
        return ['void', None, 'none']
    if len(s) < 2 or s[0] not in 'BW' or s[1] != '+':
        return ['unknown', None, 'none']
    who = s[0]
    rest = s[2:]
    if rest == '':
        return [who, None, 'unknown']
    word = rest.lower()
    for key, reason in (('r', 'resign'), ('t', 'time'), ('f', 'forfeit')):
        if word in (key, reason):
            return [who, None, reason]
    if '.' in rest:
        whole, _, frac = rest.partition('.')
    else:
        whole, frac = rest, '0'
    if not whole.isdigit() or not frac.isdigit() or len(frac) != 1:
        return ['unknown', None, 'none']
    return [who, int(whole) * 10, 'score']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['B+3.5', ['B', 35, 'score']], ['W+0.5', ['W', 5, 'score']], ['W+12', ['W', 120, 'score']], ['B+2.3', ['B', 23, 'score']], ['W+8.2', ['W', 82, 'score']], ['B+1.1', ['B', 11, 'score']], ['B+', ['B', None, 'unknown']], ['W+', ['W', None, 'unknown']]], [['W+8.2', ['W', 82, 'score']], ['0', ['draw', 0, 'score']], ['Draw', ['draw', 0, 'score']], ['Jigo', ['draw', 0, 'score']], ['Void', ['void', None, 'none']], ['?', ['unknown', None, 'none']], [' B+R ', ['B', None, 'resign']], ['W+4.1', ['W', 41, 'score']]], [['B+3.5 ', ['B', 35, 'score']], [' W+7', ['W', 70, 'score']], ['X+3', ['unknown', None, 'none']], ['B-3', ['unknown', None, 'none']], ['B+3.25', ['unknown', None, 'none']], ['W+3.0', ['W', 30, 'score']], ['B+6.0', ['B', 60, 'score']], ['B+100.5', ['B', 1005, 'score']]], [['W+0.5', ['W', 5, 'score']], ['W+3.0', ['W', 30, 'score']], ['W+Timeout', ['unknown', None, 'none']], ['B+r', ['B', None, 'resign']], ['W+t', ['W', None, 'time']], ['B+100.5', ['B', 1005, 'score']], ['W+4.1', ['W', 41, 'score']], ['W+.5', ['unknown', None, 'none']]], [['B+R', ['B', None, 'resign']], ['W+Resign', ['W', None, 'resign']], ['B+resign', ['B', None, 'resign']], ['W+T', ['W', None, 'time']], ['B+Time', ['B', None, 'time']], ['B+1.1', ['B', 11, 'score']], ['B+1.1', ['B', 11, 'score']], ['B+7.', ['unknown', None, 'none']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("result 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
result case 0['B', 30, 'score']['B', 35, 'score']Failed
result case 1['W', 0, 'score']['W', 5, 'score']Failed
result case 2['W', 120, 'score']['W', 120, 'score']Passed
result case 3['B', 20, 'score']['B', 23, 'score']Failed
result case 4['W', 80, 'score']['W', 82, 'score']Failed
result case 5['B', 10, 'score']['B', 11, 'score']Failed
result case 6['B', None, 'unknown']['B', None, 'unknown']Passed
result case 7['W', None, 'unknown']['W', None, 'unknown']Passed

SHA-256 / 16f985645af57ddf4365a5d2eb1bde6d1eb0fb5aec666d6e82a0dfb104ea1fc6

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    s = x.strip()
    if s in ('0', 'Draw', 'Jigo'):
        return ['draw', 0, 'score']
    if s == 'Void':
        return ['void', None, 'none']
    if len(s) < 2 or s[0] not in 'BW' or s[1] != '+':
        return ['unknown', None, 'none']
    who = s[0]
    rest = s[2:]
    if rest == '':
        return [who, None, 'unknown']
    word = rest.lower()
    for key, reason in (('r', 'resign'), ('t', 'time'), ('f', 'forfeit')):
        if word in (key, reason):
            return [who, None, reason]
    if '.' in rest:
        whole, _, frac = rest.partition('.')
    else:
        whole, frac = rest, '0'
    if not whole.isdigit() or not frac.isdigit() or len(frac) != 1:
        return ['unknown', None, 'none']
    return [who, int(whole) * 10 + 5 * ('.' in rest), 'score']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['B+3.5', ['B', 35, 'score']], ['W+0.5', ['W', 5, 'score']], ['W+12', ['W', 120, 'score']], ['B+2.3', ['B', 23, 'score']], ['W+8.2', ['W', 82, 'score']], ['B+1.1', ['B', 11, 'score']], ['B+', ['B', None, 'unknown']], ['W+', ['W', None, 'unknown']]], [['W+8.2', ['W', 82, 'score']], ['0', ['draw', 0, 'score']], ['Draw', ['draw', 0, 'score']], ['Jigo', ['draw', 0, 'score']], ['Void', ['void', None, 'none']], ['?', ['unknown', None, 'none']], [' B+R ', ['B', None, 'resign']], ['W+4.1', ['W', 41, 'score']]], [['B+3.5 ', ['B', 35, 'score']], [' W+7', ['W', 70, 'score']], ['X+3', ['unknown', None, 'none']], ['B-3', ['unknown', None, 'none']], ['B+3.25', ['unknown', None, 'none']], ['W+3.0', ['W', 30, 'score']], ['B+6.0', ['B', 60, 'score']], ['B+100.5', ['B', 1005, 'score']]], [['W+0.5', ['W', 5, 'score']], ['W+3.0', ['W', 30, 'score']], ['W+Timeout', ['unknown', None, 'none']], ['B+r', ['B', None, 'resign']], ['W+t', ['W', None, 'time']], ['B+100.5', ['B', 1005, 'score']], ['W+4.1', ['W', 41, 'score']], ['W+.5', ['unknown', None, 'none']]], [['B+R', ['B', None, 'resign']], ['W+Resign', ['W', None, 'resign']], ['B+resign', ['B', None, 'resign']], ['W+T', ['W', None, 'time']], ['B+Time', ['B', None, 'time']], ['B+1.1', ['B', 11, 'score']], ['B+1.1', ['B', 11, 'score']], ['B+7.', ['unknown', None, 'none']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("result 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
result case 0['B', 35, 'score']['B', 35, 'score']Passed
result case 1['W', 5, 'score']['W', 5, 'score']Passed
result case 2['W', 120, 'score']['W', 120, 'score']Passed
result case 3['B', 25, 'score']['B', 23, 'score']Failed
result case 4['W', 85, 'score']['W', 82, 'score']Failed
result case 5['B', 15, 'score']['B', 11, 'score']Failed
result case 6['B', None, 'unknown']['B', None, 'unknown']Passed
result case 7['W', None, 'unknown']['W', None, 'unknown']Passed

SHA-256 / b9470b57c6e0fff1a835b1294207539814d1769d8ea369ddf65f2d944408aeb8

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    s = x.strip()
    if s in ('0', 'Draw', 'Jigo'):
        return ['draw', 0, 'score']
    if s == 'Void':
        return ['void', None, 'none']
    if len(s) < 2 or s[0] not in 'BW' or s[1] != '+':
        return ['unknown', None, 'none']
    who = s[0]
    rest = s[2:]
    if rest == '':
        return [who, None, 'unknown']
    word = rest.lower()
    for key, reason in (('r', 'resign'), ('t', 'time'), ('f', 'forfeit')):
        if word in (key, reason):
            return [who, None, reason]
    if '.' in rest:
        whole, _, frac = rest.partition('.')
    else:
        whole, frac = rest, '0'
    if not whole.isdigit() or not frac.isdigit() or len(frac) != 1:
        return ['unknown', None, 'none']
    return [who, int(whole) * 10 + int(frac), 'score']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['B+3.5', ['B', 35, 'score']], ['W+0.5', ['W', 5, 'score']], ['W+12', ['W', 120, 'score']], ['B+2.3', ['B', 23, 'score']], ['W+8.2', ['W', 82, 'score']], ['B+1.1', ['B', 11, 'score']], ['B+', ['B', None, 'unknown']], ['W+', ['W', None, 'unknown']]], [['W+8.2', ['W', 82, 'score']], ['0', ['draw', 0, 'score']], ['Draw', ['draw', 0, 'score']], ['Jigo', ['draw', 0, 'score']], ['Void', ['void', None, 'none']], ['?', ['unknown', None, 'none']], [' B+R ', ['B', None, 'resign']], ['W+4.1', ['W', 41, 'score']]], [['B+3.5 ', ['B', 35, 'score']], [' W+7', ['W', 70, 'score']], ['X+3', ['unknown', None, 'none']], ['B-3', ['unknown', None, 'none']], ['B+3.25', ['unknown', None, 'none']], ['W+3.0', ['W', 30, 'score']], ['B+6.0', ['B', 60, 'score']], ['B+100.5', ['B', 1005, 'score']]], [['W+0.5', ['W', 5, 'score']], ['W+3.0', ['W', 30, 'score']], ['W+Timeout', ['unknown', None, 'none']], ['B+r', ['B', None, 'resign']], ['W+t', ['W', None, 'time']], ['B+100.5', ['B', 1005, 'score']], ['W+4.1', ['W', 41, 'score']], ['W+.5', ['unknown', None, 'none']]], [['B+R', ['B', None, 'resign']], ['W+Resign', ['W', None, 'resign']], ['B+resign', ['B', None, 'resign']], ['W+T', ['W', None, 'time']], ['B+Time', ['B', None, 'time']], ['B+1.1', ['B', 11, 'score']], ['B+1.1', ['B', 11, 'score']], ['B+7.', ['unknown', None, 'none']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("result 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
result case 0['B', 35, 'score']['B', 35, 'score']Passed
result case 1['W', 5, 'score']['W', 5, 'score']Passed
result case 2['W', 120, 'score']['W', 120, 'score']Passed
result case 3['B', 23, 'score']['B', 23, 'score']Passed
result case 4['W', 82, 'score']['W', 82, 'score']Passed
result case 5['B', 11, 'score']['B', 11, 'score']Passed
result case 6['B', None, 'unknown']['B', None, 'unknown']Passed
result case 7['W', None, 'unknown']['W', None, 'unknown']Passed

SHA-256 / c65b3546386e08f9b349c652e3612b8be0efccf96897a77966be5a43d1042443

Verification & scope

Small rectangular toy boards given as strings of B, W and dot; a bounded teaching model of one scoring or bookkeeping rule, not a complete rules engine or server implementation. 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:17.366920+00:00.

Case digest / da9862c456d9dc0442dad3631b0125ca7b1170ac71066545bfc48bb354a5c429