FA-83036 / Go territory scoring / Open access
Group tax is charged per stone instead of per chain · case 01
Large living groups are penalised by their size.
ROOT CAUSE
The chain counter adds the chain size.
VERIFIED REPAIR
Increment the chain count by one per chain.
Unsuccessful approach: Exempting single stones from the tax undercharges lone stones.
Case contract
Input [board, komi_tenths]. Each colour scores stones + empty regions bordered only by it, minus two points per orthogonally connected chain. Return [black, white, margin_tenths] with margin = 10*(black-white) - komi.
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):
board, komi = x
n = len(board)
m = len(board[0])
score = {'B': 0, 'W': 0}
groups = {'B': 0, 'W': 0}
seen = set()
for r in range(n):
for c in range(m):
if (r, c) in seen:
continue
col = board[r][c]
seen.add((r, c))
stack = [(r, c)]
size = 0
touch = set()
while stack:
i, j = stack.pop()
size += 1
for p, q in ((i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)):
if 0 <= p < n and 0 <= q < m:
v = board[p][q]
if v == col and (p, q) not in seen:
seen.add((p, q))
stack.append((p, q))
elif v != col:
touch.add(v)
if col == '.':
if len(touch) == 1:
score[touch.pop()] += size
else:
score[col] += size
groups[col] += size
b = score['B'] - 2 * groups['B']
w = score['W'] - 2 * groups['W']
return [b, w, b * 10 - w * 10 - komi]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['B..W', 'B..W', 'B..W', 'B..W'], 75], [2, 2, -75]], [[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 75], [8, -2, 25]], [[['BBBB', 'B..B', 'B..B', 'BBBB'], 20], [14, 0, 120]], [[['WWW', 'W.W', 'WWW'], 0], [0, 7, -70]], [[['B.W', '...', 'W.B'], 5], [-2, -2, -5]], [[['.B...', 'B....', '.....', '....W', '...W.'], 65], [-1, -1, -65]], [[['BW.', 'BW.', 'BW.'], 75], [1, 4, -105]], [[['WB.B', 'B.B.', '.B..', 'B...'], 0], [3, -1, 40]]], [[[['B.B.W', '.B.W.', 'B.BW.', 'BBW.W', '..W..'], 5], [0, 3, -35]], [[['WB.B', 'B.B.', '.B..', 'B...'], 0], [3, -1, 40]], [[['..W', '.W.', 'W..'], 5], [0, 3, -35]], [[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 65], [-4, 2, -125]], [[['..WW', '.W..', 'B...'], 75], [-1, -1, -75]], [[['..WW', '.W.B', '.WWW', 'B..B'], 20], [-3, 2, -70]], [[['W..B', 'W...', '.W.W', '.W..', 'W.W.'], 0], [-1, 0, -10]], [[['.W...', 'W....', '.....', '....W', 'W....'], 5], [0, 17, -175]]], [[[['WWW', 'W.W', 'WWW'], 0], [0, 7, -70]], [[['..B', '..B', '...', '...', '..B'], 65], [11, 0, 45]], [[['B..', 'B.W', '.W.'], 75], [0, -1, -65]], [[['W..BW.', '.W....', '......'], 20], [-1, -3, 0]], [[['BBBB', '....', '.W.B', 'BBB.', '.BBB', 'BB.B'], 0], [11, -1, 120]], [[['...', '.BW', '.B.', '...', 'B.B', 'BB.'], 5], [1, -1, 15]], [[['B.B', 'WWB', 'BWB', 'BWB', 'WBB'], 65], [3, 1, -45]], [[['B.WBB', 'W.B..', 'W.WWB', '.WB.B', '.WWWW', '..W.B'], 75], [-4, 7, -185]]], [[[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 65], [-4, 2, -125]], [[['.WWB', '.BB.', '.BBB', '..B.'], 20], [5, 0, 30]], [[['B.B...', '......', '..WWB.', '.WB.W.', 'WWB.B.'], 0], [-4, 0, -40]], [[['.WWBBB', 'WBWB.W', 'BBB.W.', 'BB..BB', 'BWB.B.', 'WWW.WW'], 5], [7, 1, 55]], [[['BBBB', 'B..B', 'W..B', '.WBB', 'WB..', '.B..'], 65], [11, -2, 65]], [[['WWWB.W', 'WB....', 'W.WB..', 'W.BB.W', 'BBW...', '.BB.WW'], 75], [2, 0, -55]], [[['BWB', 'W.W', 'WBW', 'BWB', '.W.', 'BWW'], 20], [-6, 1, -90]], [[['.BBW', '.WB.', 'W...', '....', '....'], 0], [1, -3, 40]]], [[[['W..B', 'W...', '.W.W', '.W..', 'W.W.'], 0], [-1, 0, -10]], [[['.BBW', '.WB.', 'W...', '....', '....'], 0], [1, -3, 40]], [[['B..', 'B..', '.B.', '..B', '..B'], 65], [9, 0, 25]], [[['....W.', '...B.B', 'BBBB..', '..B.W.', '.B.B.W'], 75], [5, -3, 5]], [[['..B', '.BW', 'B.B'], 20], [0, -1, -10]], [[['.BB..B', '......', 'B.B.BB', '.BBB..', '.B....'], 0], [20, 0, 200]], [[['W..', '.B.', '.W.', '...'], 5], [-1, -2, 5]], [[['..WW', 'B.WW', 'WBBW', '.WBW', 'WWBW', 'B..W'], 65], [0, 7, -135]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
check("group tax 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| group tax case 0 | [-4, -4, -75] | [2, 2, -75] | Failed |
| group tax case 1 | [6, -2, 5] | [8, -2, 25] | Failed |
| group tax case 2 | [-8, 0, -100] | [14, 0, 120] | Failed |
| group tax case 3 | [0, -7, 70] | [0, 7, -70] | Failed |
| group tax case 4 | [-2, -2, -5] | [-2, -2, -5] | Passed |
| group tax case 5 | [-1, -1, -65] | [-1, -1, -65] | Passed |
| group tax case 6 | [-3, 0, -105] | [1, 4, -105] | Failed |
| group tax case 7 | [3, -1, 40] | [3, -1, 40] | Passed |
SHA-256 / d8f69254e1d6b9aa8cfb730633ecdcb2d962d441d172b2e8a3ab292acb1636cd
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
board, komi = x
n = len(board)
m = len(board[0])
score = {'B': 0, 'W': 0}
groups = {'B': 0, 'W': 0}
seen = set()
for r in range(n):
for c in range(m):
if (r, c) in seen:
continue
col = board[r][c]
seen.add((r, c))
stack = [(r, c)]
size = 0
touch = set()
while stack:
i, j = stack.pop()
size += 1
for p, q in ((i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)):
if 0 <= p < n and 0 <= q < m:
v = board[p][q]
if v == col and (p, q) not in seen:
seen.add((p, q))
stack.append((p, q))
elif v != col:
touch.add(v)
if col == '.':
if len(touch) == 1:
score[touch.pop()] += size
else:
score[col] += size
groups[col] += 1 if size > 1 else 0
b = score['B'] - 2 * groups['B']
w = score['W'] - 2 * groups['W']
return [b, w, b * 10 - w * 10 - komi]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['B..W', 'B..W', 'B..W', 'B..W'], 75], [2, 2, -75]], [[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 75], [8, -2, 25]], [[['BBBB', 'B..B', 'B..B', 'BBBB'], 20], [14, 0, 120]], [[['WWW', 'W.W', 'WWW'], 0], [0, 7, -70]], [[['B.W', '...', 'W.B'], 5], [-2, -2, -5]], [[['.B...', 'B....', '.....', '....W', '...W.'], 65], [-1, -1, -65]], [[['BW.', 'BW.', 'BW.'], 75], [1, 4, -105]], [[['WB.B', 'B.B.', '.B..', 'B...'], 0], [3, -1, 40]]], [[[['B.B.W', '.B.W.', 'B.BW.', 'BBW.W', '..W..'], 5], [0, 3, -35]], [[['WB.B', 'B.B.', '.B..', 'B...'], 0], [3, -1, 40]], [[['..W', '.W.', 'W..'], 5], [0, 3, -35]], [[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 65], [-4, 2, -125]], [[['..WW', '.W..', 'B...'], 75], [-1, -1, -75]], [[['..WW', '.W.B', '.WWW', 'B..B'], 20], [-3, 2, -70]], [[['W..B', 'W...', '.W.W', '.W..', 'W.W.'], 0], [-1, 0, -10]], [[['.W...', 'W....', '.....', '....W', 'W....'], 5], [0, 17, -175]]], [[[['WWW', 'W.W', 'WWW'], 0], [0, 7, -70]], [[['..B', '..B', '...', '...', '..B'], 65], [11, 0, 45]], [[['B..', 'B.W', '.W.'], 75], [0, -1, -65]], [[['W..BW.', '.W....', '......'], 20], [-1, -3, 0]], [[['BBBB', '....', '.W.B', 'BBB.', '.BBB', 'BB.B'], 0], [11, -1, 120]], [[['...', '.BW', '.B.', '...', 'B.B', 'BB.'], 5], [1, -1, 15]], [[['B.B', 'WWB', 'BWB', 'BWB', 'WBB'], 65], [3, 1, -45]], [[['B.WBB', 'W.B..', 'W.WWB', '.WB.B', '.WWWW', '..W.B'], 75], [-4, 7, -185]]], [[[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 65], [-4, 2, -125]], [[['.WWB', '.BB.', '.BBB', '..B.'], 20], [5, 0, 30]], [[['B.B...', '......', '..WWB.', '.WB.W.', 'WWB.B.'], 0], [-4, 0, -40]], [[['.WWBBB', 'WBWB.W', 'BBB.W.', 'BB..BB', 'BWB.B.', 'WWW.WW'], 5], [7, 1, 55]], [[['BBBB', 'B..B', 'W..B', '.WBB', 'WB..', '.B..'], 65], [11, -2, 65]], [[['WWWB.W', 'WB....', 'W.WB..', 'W.BB.W', 'BBW...', '.BB.WW'], 75], [2, 0, -55]], [[['BWB', 'W.W', 'WBW', 'BWB', '.W.', 'BWW'], 20], [-6, 1, -90]], [[['.BBW', '.WB.', 'W...', '....', '....'], 0], [1, -3, 40]]], [[[['W..B', 'W...', '.W.W', '.W..', 'W.W.'], 0], [-1, 0, -10]], [[['.BBW', '.WB.', 'W...', '....', '....'], 0], [1, -3, 40]], [[['B..', 'B..', '.B.', '..B', '..B'], 65], [9, 0, 25]], [[['....W.', '...B.B', 'BBBB..', '..B.W.', '.B.B.W'], 75], [5, -3, 5]], [[['..B', '.BW', 'B.B'], 20], [0, -1, -10]], [[['.BB..B', '......', 'B.B.BB', '.BBB..', '.B....'], 0], [20, 0, 200]], [[['W..', '.B.', '.W.', '...'], 5], [-1, -2, 5]], [[['..WW', 'B.WW', 'WBBW', '.WBW', 'WWBW', 'B..W'], 65], [0, 7, -135]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
check("group tax 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| group tax case 0 | [2, 2, -75] | [2, 2, -75] | Passed |
| group tax case 1 | [14, 8, -15] | [8, -2, 25] | Failed |
| group tax case 2 | [14, 0, 120] | [14, 0, 120] | Passed |
| group tax case 3 | [0, 7, -70] | [0, 7, -70] | Passed |
| group tax case 4 | [2, 2, -5] | [-2, -2, -5] | Failed |
| group tax case 5 | [3, 3, -65] | [-1, -1, -65] | Failed |
| group tax case 6 | [1, 4, -105] | [1, 4, -105] | Passed |
| group tax case 7 | [15, 1, 140] | [3, -1, 40] | Failed |
SHA-256 / fe8b589cc461c0617f2f3f30c64586453a3e629f5674dee133f8ae155065ad4a
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
board, komi = x
n = len(board)
m = len(board[0])
score = {'B': 0, 'W': 0}
groups = {'B': 0, 'W': 0}
seen = set()
for r in range(n):
for c in range(m):
if (r, c) in seen:
continue
col = board[r][c]
seen.add((r, c))
stack = [(r, c)]
size = 0
touch = set()
while stack:
i, j = stack.pop()
size += 1
for p, q in ((i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)):
if 0 <= p < n and 0 <= q < m:
v = board[p][q]
if v == col and (p, q) not in seen:
seen.add((p, q))
stack.append((p, q))
elif v != col:
touch.add(v)
if col == '.':
if len(touch) == 1:
score[touch.pop()] += size
else:
score[col] += size
groups[col] += 1
b = score['B'] - 2 * groups['B']
w = score['W'] - 2 * groups['W']
return [b, w, b * 10 - w * 10 - komi]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['B..W', 'B..W', 'B..W', 'B..W'], 75], [2, 2, -75]], [[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 75], [8, -2, 25]], [[['BBBB', 'B..B', 'B..B', 'BBBB'], 20], [14, 0, 120]], [[['WWW', 'W.W', 'WWW'], 0], [0, 7, -70]], [[['B.W', '...', 'W.B'], 5], [-2, -2, -5]], [[['.B...', 'B....', '.....', '....W', '...W.'], 65], [-1, -1, -65]], [[['BW.', 'BW.', 'BW.'], 75], [1, 4, -105]], [[['WB.B', 'B.B.', '.B..', 'B...'], 0], [3, -1, 40]]], [[[['B.B.W', '.B.W.', 'B.BW.', 'BBW.W', '..W..'], 5], [0, 3, -35]], [[['WB.B', 'B.B.', '.B..', 'B...'], 0], [3, -1, 40]], [[['..W', '.W.', 'W..'], 5], [0, 3, -35]], [[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 65], [-4, 2, -125]], [[['..WW', '.W..', 'B...'], 75], [-1, -1, -75]], [[['..WW', '.W.B', '.WWW', 'B..B'], 20], [-3, 2, -70]], [[['W..B', 'W...', '.W.W', '.W..', 'W.W.'], 0], [-1, 0, -10]], [[['.W...', 'W....', '.....', '....W', 'W....'], 5], [0, 17, -175]]], [[[['WWW', 'W.W', 'WWW'], 0], [0, 7, -70]], [[['..B', '..B', '...', '...', '..B'], 65], [11, 0, 45]], [[['B..', 'B.W', '.W.'], 75], [0, -1, -65]], [[['W..BW.', '.W....', '......'], 20], [-1, -3, 0]], [[['BBBB', '....', '.W.B', 'BBB.', '.BBB', 'BB.B'], 0], [11, -1, 120]], [[['...', '.BW', '.B.', '...', 'B.B', 'BB.'], 5], [1, -1, 15]], [[['B.B', 'WWB', 'BWB', 'BWB', 'WBB'], 65], [3, 1, -45]], [[['B.WBB', 'W.B..', 'W.WWB', '.WB.B', '.WWWW', '..W.B'], 75], [-4, 7, -185]]], [[[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 65], [-4, 2, -125]], [[['.WWB', '.BB.', '.BBB', '..B.'], 20], [5, 0, 30]], [[['B.B...', '......', '..WWB.', '.WB.W.', 'WWB.B.'], 0], [-4, 0, -40]], [[['.WWBBB', 'WBWB.W', 'BBB.W.', 'BB..BB', 'BWB.B.', 'WWW.WW'], 5], [7, 1, 55]], [[['BBBB', 'B..B', 'W..B', '.WBB', 'WB..', '.B..'], 65], [11, -2, 65]], [[['WWWB.W', 'WB....', 'W.WB..', 'W.BB.W', 'BBW...', '.BB.WW'], 75], [2, 0, -55]], [[['BWB', 'W.W', 'WBW', 'BWB', '.W.', 'BWW'], 20], [-6, 1, -90]], [[['.BBW', '.WB.', 'W...', '....', '....'], 0], [1, -3, 40]]], [[[['W..B', 'W...', '.W.W', '.W..', 'W.W.'], 0], [-1, 0, -10]], [[['.BBW', '.WB.', 'W...', '....', '....'], 0], [1, -3, 40]], [[['B..', 'B..', '.B.', '..B', '..B'], 65], [9, 0, 25]], [[['....W.', '...B.B', 'BBBB..', '..B.W.', '.B.B.W'], 75], [5, -3, 5]], [[['..B', '.BW', 'B.B'], 20], [0, -1, -10]], [[['.BB..B', '......', 'B.B.BB', '.BBB..', '.B....'], 0], [20, 0, 200]], [[['W..', '.B.', '.W.', '...'], 5], [-1, -2, 5]], [[['..WW', 'B.WW', 'WBBW', '.WBW', 'WWBW', 'B..W'], 65], [0, 7, -135]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
check("group tax 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| group tax case 0 | [2, 2, -75] | [2, 2, -75] | Passed |
| group tax case 1 | [8, -2, 25] | [8, -2, 25] | Passed |
| group tax case 2 | [14, 0, 120] | [14, 0, 120] | Passed |
| group tax case 3 | [0, 7, -70] | [0, 7, -70] | Passed |
| group tax case 4 | [-2, -2, -5] | [-2, -2, -5] | Passed |
| group tax case 5 | [-1, -1, -65] | [-1, -1, -65] | Passed |
| group tax case 6 | [1, 4, -105] | [1, 4, -105] | Passed |
| group tax case 7 | [3, -1, 40] | [3, -1, 40] | Passed |
SHA-256 / c37636e82d1081047ff24d92ca1cd05d259883f037bc0f44a21ec47022635c79
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.902694+00:00.
Case digest / e48890eb74639bed4eb40957db590a0fcc3575bcf8670d980b0e0310a6cfee0b