FA-82861 / Go territory scoring / Open access
Region seed point is counted twice in territory · case 01
Some enclosed regions are one point larger than they are on the board.
ROOT CAUSE
The starting point of a flood fill is pushed without being marked visited, so a neighbour pushes it again.
VERIFIED REPAIR
Mark each point visited at the moment it is pushed, including the seed.
Unsuccessful approach: Marking points visited only when popped lets a point be queued by several neighbours before it is processed.
Case contract
Input [board, komi_tenths]. Area = own stones + empty regions whose orthogonal border touches only that colour. Return [black_area, white_area, result] where result is "B+x.y" / "W+x.y" after subtracting komi from black, or "Draw".
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])
b = sum(row.count('B') for row in board)
w = sum(row.count('W') for row in board)
seen = set()
for r in range(n):
for c in range(m):
if board[r][c] != '.' or (r, c) in seen:
continue
stack = [(r, c)]
size = 0
border = set()
while stack:
i, j = stack.pop()
size += 1
for di, dj in ((1, 0), (-1, 0), (0, 1), (0, -1)):
a, d = i + di, j + dj
if 0 <= a < n and 0 <= d < m:
v = board[a][d]
if v == '.':
if (a, d) not in seen:
seen.add((a, d))
stack.append((a, d))
else:
border.add(v)
if border == {'B'}:
b += size
elif border == {'W'}:
w += size
diff = b * 10 - w * 10 - komi
if diff > 0:
res = 'B+%d.%d' % (diff // 10, diff % 10)
elif diff < 0:
res = 'W+%d.%d' % ((-diff) // 10, (-diff) % 10)
else:
res = 'Draw'
return [b, w, res]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['.BW.', '.BW.', '.BW.', '.BW.'], 35], [8, 8, 'W+3.5']], [[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['BBBB', 'B..B', 'B..B', 'BBBB'], 5], [16, 0, 'B+15.5']], [[['WWW', 'W.W', 'WWW'], 65], [0, 9, 'W+15.5']], [[['B.W', '...', 'W.B'], 75], [2, 2, 'W+7.5']], [[['.B...', 'B....', '.....', '....W', '...W.'], 35], [3, 3, 'W+3.5']], [[['BW.', 'BW.', 'BW.'], 60], [3, 6, 'W+9.0']], [[['B..', '.B.', '...', '...', 'B..', 'BB.'], 70], [18, 0, 'B+11.0']]], [[[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['WB.B', 'B.B.', '.B..', 'B...'], -5], [15, 1, 'B+14.5']], [[['..W', '.W.', 'W..'], 0], [0, 9, 'W+9.0']], [[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 5], [4, 4, 'W+0.5']], [[['.BBW.', 'B.BBB', '...W.', '.B.WB'], 65], [9, 3, 'W+0.5']], [[['BB.W.', 'BB.WW', 'W..WW', '.W...', '.B...'], 75], [5, 8, 'W+10.5']], [[['B..', '..W', '...', '.WW'], 35], [1, 3, 'W+5.5']], [[['.BW..', '.BW..', '.BW..', '.BW..', '.BW..', '.BW..'], 70], [12, 18, 'W+13.0']]], [[[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['.....', '.BBB.', '.B.B.', '.BBB.', '.....'], 70], [25, 0, 'B+18.0']], [[['WBWWB', 'BB.B.', 'WBWB.', 'WBWBB'], -5], [12, 7, 'B+5.5']], [[['...', '...', '.B.', '.WB', '...', 'B..'], 0], [3, 1, 'B+2.0']], [[['.....', '.W...', 'WW...'], 5], [0, 15, 'W+15.5']], [[['.B.', '.WW', 'BBB', 'WW.'], 65], [4, 4, 'W+6.5']], [[['.B.BB', 'WB...', '.....'], 75], [4, 1, 'W+4.5']], [[['.WBB', 'BW..', '..B.'], 35], [4, 2, 'W+1.5']]], [[[['WBWWB', 'BB.B.', 'WBWB.', 'WBWBB'], -5], [12, 7, 'B+5.5']], [[['B.BW.', 'W.B..', 'W..B.', 'B.B.B'], 60], [8, 3, 'W+1.0']], [[['B..', '.B.', '...', '...', 'B..', 'BB.'], 70], [18, 0, 'B+11.0']], [[['BW.B', '.WWW', 'B.BB', 'WBB.', 'BW.W'], -5], [8, 7, 'B+1.5']], [[['......', '..W...', 'WW.WW.', '......', 'WBW...'], 0], [1, 7, 'W+6.0']], [[['W..W', '.B..', 'B.W.', '.W..', '....'], 5], [2, 4, 'W+2.5']], [[['BWBB.', '.B..B', 'BW..B', 'W.BBW', '...BB'], 65], [13, 4, 'B+2.5']], [[['...W..', '..WBW.', 'W...WW', '.WW..W'], 75], [1, 13, 'W+19.5']]], [[[['...W..', '..WBW.', 'W...WW', '.WW..W'], 75], [1, 13, 'W+19.5']], [[['....W', 'BBWWB', 'B..W.', 'W..W.', 'W.W..', '.....'], 35], [4, 8, 'W+7.5']], [[['W..B.', 'BB.BB', 'B..B.', 'BBB..', 'B.BBB'], 60], [19, 1, 'B+12.0']], [[['.BBBW.', 'WBBBB.', '.WBB..', 'BBB...', 'BBW..B', '..BW.W'], 70], [18, 6, 'B+5.0']], [[['W..BBB', 'BWB.W.', '.WWWWW', 'W.WBWW'], -5], [6, 13, 'W+6.5']], [[['W..B', '...B', 'WWWW'], 0], [2, 5, 'W+3.0']], [[['W...', 'WBWB', 'WWBW', 'BBB.'], 5], [6, 6, 'W+0.5']], [[['.BW..', '.BW..', '.BW..', '.BW..', '.BW..', '.BW..'], 70], [12, 18, 'W+13.0']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
check("area 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| area score case 0 | [9, 9, 'W+3.5'] | [8, 8, 'W+3.5'] | Failed |
| area score case 1 | [17, 8, 'B+9.0'] | [16, 8, 'B+8.0'] | Failed |
| area score case 2 | [17, 0, 'B+16.5'] | [16, 0, 'B+15.5'] | Failed |
| area score case 3 | [0, 9, 'W+15.5'] | [0, 9, 'W+15.5'] | Passed |
| area score case 4 | [2, 2, 'W+7.5'] | [2, 2, 'W+7.5'] | Passed |
| area score case 5 | [3, 3, 'W+3.5'] | [3, 3, 'W+3.5'] | Passed |
| area score case 6 | [3, 7, 'W+10.0'] | [3, 6, 'W+9.0'] | Failed |
| area score case 7 | [19, 0, 'B+12.0'] | [18, 0, 'B+11.0'] | Failed |
SHA-256 / f23a204e52357c3d2779a1ffdee109cd8d5d8b32c8d698688aece8b2278332cc
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])
b = sum(row.count('B') for row in board)
w = sum(row.count('W') for row in board)
seen = set()
for r in range(n):
for c in range(m):
if board[r][c] != '.' or (r, c) in seen:
continue
stack = [(r, c)]
size = 0
border = set()
while stack:
i, j = stack.pop()
seen.add((i, j))
size += 1
for di, dj in ((1, 0), (-1, 0), (0, 1), (0, -1)):
a, d = i + di, j + dj
if 0 <= a < n and 0 <= d < m:
v = board[a][d]
if v == '.':
if (a, d) not in seen:
stack.append((a, d))
else:
border.add(v)
if border == {'B'}:
b += size
elif border == {'W'}:
w += size
diff = b * 10 - w * 10 - komi
if diff > 0:
res = 'B+%d.%d' % (diff // 10, diff % 10)
elif diff < 0:
res = 'W+%d.%d' % ((-diff) // 10, (-diff) % 10)
else:
res = 'Draw'
return [b, w, res]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['.BW.', '.BW.', '.BW.', '.BW.'], 35], [8, 8, 'W+3.5']], [[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['BBBB', 'B..B', 'B..B', 'BBBB'], 5], [16, 0, 'B+15.5']], [[['WWW', 'W.W', 'WWW'], 65], [0, 9, 'W+15.5']], [[['B.W', '...', 'W.B'], 75], [2, 2, 'W+7.5']], [[['.B...', 'B....', '.....', '....W', '...W.'], 35], [3, 3, 'W+3.5']], [[['BW.', 'BW.', 'BW.'], 60], [3, 6, 'W+9.0']], [[['B..', '.B.', '...', '...', 'B..', 'BB.'], 70], [18, 0, 'B+11.0']]], [[[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['WB.B', 'B.B.', '.B..', 'B...'], -5], [15, 1, 'B+14.5']], [[['..W', '.W.', 'W..'], 0], [0, 9, 'W+9.0']], [[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 5], [4, 4, 'W+0.5']], [[['.BBW.', 'B.BBB', '...W.', '.B.WB'], 65], [9, 3, 'W+0.5']], [[['BB.W.', 'BB.WW', 'W..WW', '.W...', '.B...'], 75], [5, 8, 'W+10.5']], [[['B..', '..W', '...', '.WW'], 35], [1, 3, 'W+5.5']], [[['.BW..', '.BW..', '.BW..', '.BW..', '.BW..', '.BW..'], 70], [12, 18, 'W+13.0']]], [[[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['.....', '.BBB.', '.B.B.', '.BBB.', '.....'], 70], [25, 0, 'B+18.0']], [[['WBWWB', 'BB.B.', 'WBWB.', 'WBWBB'], -5], [12, 7, 'B+5.5']], [[['...', '...', '.B.', '.WB', '...', 'B..'], 0], [3, 1, 'B+2.0']], [[['.....', '.W...', 'WW...'], 5], [0, 15, 'W+15.5']], [[['.B.', '.WW', 'BBB', 'WW.'], 65], [4, 4, 'W+6.5']], [[['.B.BB', 'WB...', '.....'], 75], [4, 1, 'W+4.5']], [[['.WBB', 'BW..', '..B.'], 35], [4, 2, 'W+1.5']]], [[[['WBWWB', 'BB.B.', 'WBWB.', 'WBWBB'], -5], [12, 7, 'B+5.5']], [[['B.BW.', 'W.B..', 'W..B.', 'B.B.B'], 60], [8, 3, 'W+1.0']], [[['B..', '.B.', '...', '...', 'B..', 'BB.'], 70], [18, 0, 'B+11.0']], [[['BW.B', '.WWW', 'B.BB', 'WBB.', 'BW.W'], -5], [8, 7, 'B+1.5']], [[['......', '..W...', 'WW.WW.', '......', 'WBW...'], 0], [1, 7, 'W+6.0']], [[['W..W', '.B..', 'B.W.', '.W..', '....'], 5], [2, 4, 'W+2.5']], [[['BWBB.', '.B..B', 'BW..B', 'W.BBW', '...BB'], 65], [13, 4, 'B+2.5']], [[['...W..', '..WBW.', 'W...WW', '.WW..W'], 75], [1, 13, 'W+19.5']]], [[[['...W..', '..WBW.', 'W...WW', '.WW..W'], 75], [1, 13, 'W+19.5']], [[['....W', 'BBWWB', 'B..W.', 'W..W.', 'W.W..', '.....'], 35], [4, 8, 'W+7.5']], [[['W..B.', 'BB.BB', 'B..B.', 'BBB..', 'B.BBB'], 60], [19, 1, 'B+12.0']], [[['.BBBW.', 'WBBBB.', '.WBB..', 'BBB...', 'BBW..B', '..BW.W'], 70], [18, 6, 'B+5.0']], [[['W..BBB', 'BWB.W.', '.WWWWW', 'W.WBWW'], -5], [6, 13, 'W+6.5']], [[['W..B', '...B', 'WWWW'], 0], [2, 5, 'W+3.0']], [[['W...', 'WBWB', 'WWBW', 'BBB.'], 5], [6, 6, 'W+0.5']], [[['.BW..', '.BW..', '.BW..', '.BW..', '.BW..', '.BW..'], 70], [12, 18, 'W+13.0']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
check("area 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| area score case 0 | [8, 8, 'W+3.5'] | [8, 8, 'W+3.5'] | Passed |
| area score case 1 | [19, 8, 'B+11.0'] | [16, 8, 'B+8.0'] | Failed |
| area score case 2 | [17, 0, 'B+16.5'] | [16, 0, 'B+15.5'] | Failed |
| area score case 3 | [0, 9, 'W+15.5'] | [0, 9, 'W+15.5'] | Passed |
| area score case 4 | [2, 2, 'W+7.5'] | [2, 2, 'W+7.5'] | Passed |
| area score case 5 | [3, 3, 'W+3.5'] | [3, 3, 'W+3.5'] | Passed |
| area score case 6 | [3, 6, 'W+9.0'] | [3, 6, 'W+9.0'] | Passed |
| area score case 7 | [21, 0, 'B+14.0'] | [18, 0, 'B+11.0'] | Failed |
SHA-256 / 69f5bf5c98acc4537eee149e1b5d8597795bf120cefbbe59ee044e23e46e04c2
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])
b = sum(row.count('B') for row in board)
w = sum(row.count('W') for row in board)
seen = set()
for r in range(n):
for c in range(m):
if board[r][c] != '.' or (r, c) in seen:
continue
stack = [(r, c)]
seen.add((r, c))
size = 0
border = set()
while stack:
i, j = stack.pop()
size += 1
for di, dj in ((1, 0), (-1, 0), (0, 1), (0, -1)):
a, d = i + di, j + dj
if 0 <= a < n and 0 <= d < m:
v = board[a][d]
if v == '.':
if (a, d) not in seen:
seen.add((a, d))
stack.append((a, d))
else:
border.add(v)
if border == {'B'}:
b += size
elif border == {'W'}:
w += size
diff = b * 10 - w * 10 - komi
if diff > 0:
res = 'B+%d.%d' % (diff // 10, diff % 10)
elif diff < 0:
res = 'W+%d.%d' % ((-diff) // 10, (-diff) % 10)
else:
res = 'Draw'
return [b, w, res]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['.BW.', '.BW.', '.BW.', '.BW.'], 35], [8, 8, 'W+3.5']], [[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['BBBB', 'B..B', 'B..B', 'BBBB'], 5], [16, 0, 'B+15.5']], [[['WWW', 'W.W', 'WWW'], 65], [0, 9, 'W+15.5']], [[['B.W', '...', 'W.B'], 75], [2, 2, 'W+7.5']], [[['.B...', 'B....', '.....', '....W', '...W.'], 35], [3, 3, 'W+3.5']], [[['BW.', 'BW.', 'BW.'], 60], [3, 6, 'W+9.0']], [[['B..', '.B.', '...', '...', 'B..', 'BB.'], 70], [18, 0, 'B+11.0']]], [[[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['WB.B', 'B.B.', '.B..', 'B...'], -5], [15, 1, 'B+14.5']], [[['..W', '.W.', 'W..'], 0], [0, 9, 'W+9.0']], [[['B....B', '......', '..WW..', '..WW..', '......', 'B....B'], 5], [4, 4, 'W+0.5']], [[['.BBW.', 'B.BBB', '...W.', '.B.WB'], 65], [9, 3, 'W+0.5']], [[['BB.W.', 'BB.WW', 'W..WW', '.W...', '.B...'], 75], [5, 8, 'W+10.5']], [[['B..', '..W', '...', '.WW'], 35], [1, 3, 'W+5.5']], [[['.BW..', '.BW..', '.BW..', '.BW..', '.BW..', '.BW..'], 70], [12, 18, 'W+13.0']]], [[[['.W.B.', 'W.WB.', '.WB..', 'WB...', 'B....'], 0], [16, 8, 'B+8.0']], [[['.....', '.BBB.', '.B.B.', '.BBB.', '.....'], 70], [25, 0, 'B+18.0']], [[['WBWWB', 'BB.B.', 'WBWB.', 'WBWBB'], -5], [12, 7, 'B+5.5']], [[['...', '...', '.B.', '.WB', '...', 'B..'], 0], [3, 1, 'B+2.0']], [[['.....', '.W...', 'WW...'], 5], [0, 15, 'W+15.5']], [[['.B.', '.WW', 'BBB', 'WW.'], 65], [4, 4, 'W+6.5']], [[['.B.BB', 'WB...', '.....'], 75], [4, 1, 'W+4.5']], [[['.WBB', 'BW..', '..B.'], 35], [4, 2, 'W+1.5']]], [[[['WBWWB', 'BB.B.', 'WBWB.', 'WBWBB'], -5], [12, 7, 'B+5.5']], [[['B.BW.', 'W.B..', 'W..B.', 'B.B.B'], 60], [8, 3, 'W+1.0']], [[['B..', '.B.', '...', '...', 'B..', 'BB.'], 70], [18, 0, 'B+11.0']], [[['BW.B', '.WWW', 'B.BB', 'WBB.', 'BW.W'], -5], [8, 7, 'B+1.5']], [[['......', '..W...', 'WW.WW.', '......', 'WBW...'], 0], [1, 7, 'W+6.0']], [[['W..W', '.B..', 'B.W.', '.W..', '....'], 5], [2, 4, 'W+2.5']], [[['BWBB.', '.B..B', 'BW..B', 'W.BBW', '...BB'], 65], [13, 4, 'B+2.5']], [[['...W..', '..WBW.', 'W...WW', '.WW..W'], 75], [1, 13, 'W+19.5']]], [[[['...W..', '..WBW.', 'W...WW', '.WW..W'], 75], [1, 13, 'W+19.5']], [[['....W', 'BBWWB', 'B..W.', 'W..W.', 'W.W..', '.....'], 35], [4, 8, 'W+7.5']], [[['W..B.', 'BB.BB', 'B..B.', 'BBB..', 'B.BBB'], 60], [19, 1, 'B+12.0']], [[['.BBBW.', 'WBBBB.', '.WBB..', 'BBB...', 'BBW..B', '..BW.W'], 70], [18, 6, 'B+5.0']], [[['W..BBB', 'BWB.W.', '.WWWWW', 'W.WBWW'], -5], [6, 13, 'W+6.5']], [[['W..B', '...B', 'WWWW'], 0], [2, 5, 'W+3.0']], [[['W...', 'WBWB', 'WWBW', 'BBB.'], 5], [6, 6, 'W+0.5']], [[['.BW..', '.BW..', '.BW..', '.BW..', '.BW..', '.BW..'], 70], [12, 18, 'W+13.0']]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
check("area 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| area score case 0 | [8, 8, 'W+3.5'] | [8, 8, 'W+3.5'] | Passed |
| area score case 1 | [16, 8, 'B+8.0'] | [16, 8, 'B+8.0'] | Passed |
| area score case 2 | [16, 0, 'B+15.5'] | [16, 0, 'B+15.5'] | Passed |
| area score case 3 | [0, 9, 'W+15.5'] | [0, 9, 'W+15.5'] | Passed |
| area score case 4 | [2, 2, 'W+7.5'] | [2, 2, 'W+7.5'] | Passed |
| area score case 5 | [3, 3, 'W+3.5'] | [3, 3, 'W+3.5'] | Passed |
| area score case 6 | [3, 6, 'W+9.0'] | [3, 6, 'W+9.0'] | Passed |
| area score case 7 | [18, 0, 'B+11.0'] | [18, 0, 'B+11.0'] | Passed |
SHA-256 / b599cad78012380858141964046c0e8ee250cc726986b7c00c837bb5e2e6688e
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:15.980715+00:00.
Case digest / bc9d7439cd78f2a035aad280db3bf2b218d95f738af2d43054d5a6f397559723