FA-83041 / Go territory scoring / Open access
Diagonally touching stones are merged into one taxed chain · case 01
Players are under-taxed for diagonal shapes and regions leak diagonally.
ROOT CAUSE
The neighbour list includes diagonal offsets.
VERIFIED REPAIR
Connect only orthogonal neighbours.
Unsuccessful approach: Adding one diagonal direction only for stones still merges some diagonal pairs.
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), (i - 1, j - 1), (i + 1, j + 1), (i - 1, j + 1), (i + 1, 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.', 'B.B', '.B.'], 65], [1, 0, -55]], [[['.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]], [[['B.B', 'WWB', 'BWB', 'BWB', 'WBB'], 65], [3, 1, -45]]], [[[['.B...', 'B....', '.....', '....W', '...W.'], 65], [-1, -1, -65]], [[['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]], [[['BWB', 'W.W', 'WBW', 'BWB', '.W.', 'BWW'], 20], [-6, 1, -90]]], [[[['..WW', '.W..', 'B...'], 75], [-1, -1, -75]], [[['..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]], [[['..WW', 'B.WW', 'WBBW', '.WBW', 'WWBW', 'B..W'], 65], [0, 7, -135]]], [[[['.W...', 'W....', '.....', '....W', 'W....'], 5], [0, 17, -175]], [[['.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]], [[['..W.W.', '.W.W..', 'W..B..', 'W.WW..', 'WBBWW.', '.B....'], 20], [0, 3, -50]]], [[[['BBBB', '....', '.W.B', 'BBB.', '.BBB', 'BB.B'], 0], [11, -1, 120]], [[['WWBBW', 'BB.WW', 'W.WW.'], 5], [0, 3, -35]], [[['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]], [[['.BW.', '..W.', 'B..W', '..WW', '.WWB', '.WBW'], 0], [-4, 5, -90]]]]
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 | [7, 0, 5] | [1, 0, -55] | Failed |
| group tax case 1 | [3, 3, -75] | [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] | Passed |
| group tax case 5 | [0, 0, -65] | [-1, -1, -65] | Failed |
| group tax case 6 | [1, 4, -105] | [1, 4, -105] | Passed |
| group tax case 7 | [5, 3, -45] | [3, 1, -45] | Failed |
SHA-256 / ca59330c038656baf95d0f490e3f879e3505c0f0019f7f51aae44578e3aee89d
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)) + (((i - 1, j - 1), (i + 1, j + 1)) if col != '.' else ()):
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.', 'B.B', '.B.'], 65], [1, 0, -55]], [[['.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]], [[['B.B', 'WWB', 'BWB', 'BWB', 'WBB'], 65], [3, 1, -45]]], [[[['.B...', 'B....', '.....', '....W', '...W.'], 65], [-1, -1, -65]], [[['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]], [[['BWB', 'W.W', 'WBW', 'BWB', '.W.', 'BWW'], 20], [-6, 1, -90]]], [[[['..WW', '.W..', 'B...'], 75], [-1, -1, -75]], [[['..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]], [[['..WW', 'B.WW', 'WBBW', '.WBW', 'WWBW', 'B..W'], 65], [0, 7, -135]]], [[[['.W...', 'W....', '.....', '....W', 'W....'], 5], [0, 17, -175]], [[['.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]], [[['..W.W.', '.W.W..', 'W..B..', 'W.WW..', 'WBBWW.', '.B....'], 20], [0, 3, -50]]], [[[['BBBB', '....', '.W.B', 'BBB.', '.BBB', 'BB.B'], 0], [11, -1, 120]], [[['WWBBW', 'BB.WW', 'W.WW.'], 5], [0, 3, -35]], [[['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]], [[['.BW.', '..W.', 'B..W', '..WW', '.WWB', '.WBW'], 0], [-4, 5, -90]]]]
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 | [5, 0, -15] | [1, 0, -55] | Failed |
| group tax case 1 | [8, 2, -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] | 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 | [5, 1, -25] | [3, 1, -45] | Failed |
SHA-256 / 26a83209262a8390f29a20a6adaa6d7f7cfa430f124b6c410f2724eaf68b4bd2
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.', 'B.B', '.B.'], 65], [1, 0, -55]], [[['.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]], [[['B.B', 'WWB', 'BWB', 'BWB', 'WBB'], 65], [3, 1, -45]]], [[[['.B...', 'B....', '.....', '....W', '...W.'], 65], [-1, -1, -65]], [[['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]], [[['BWB', 'W.W', 'WBW', 'BWB', '.W.', 'BWW'], 20], [-6, 1, -90]]], [[[['..WW', '.W..', 'B...'], 75], [-1, -1, -75]], [[['..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]], [[['..WW', 'B.WW', 'WBBW', '.WBW', 'WWBW', 'B..W'], 65], [0, 7, -135]]], [[[['.W...', 'W....', '.....', '....W', 'W....'], 5], [0, 17, -175]], [[['.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]], [[['..W.W.', '.W.W..', 'W..B..', 'W.WW..', 'WBBWW.', '.B....'], 20], [0, 3, -50]]], [[[['BBBB', '....', '.W.B', 'BBB.', '.BBB', 'BB.B'], 0], [11, -1, 120]], [[['WWBBW', 'BB.WW', 'W.WW.'], 5], [0, 3, -35]], [[['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]], [[['.BW.', '..W.', 'B..W', '..WW', '.WWB', '.WBW'], 0], [-4, 5, -90]]]]
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 | [1, 0, -55] | [1, 0, -55] | 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, -45] | [3, 1, -45] | Passed |
SHA-256 / 253787e41847bba94ee466b227671be61d6ffe31bcc83979319cff750de69e5b
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:18.002998+00:00.
Case digest / 99f73f29cc6b677b36a80adaf89b0834a432422e91458e5fed34b7ce9aaeeeaf