FAILURE MAP
← Case archive

FA-83331 / Go territory scoring / Open access

Dead stones are still counted as area for their owner · case 01

A dead group adds its stones to its owner area.

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

ROOT CAUSE

Stones are counted from the original board before dead removal.

VERIFIED REPAIR

Count stones on the board after dead stones are removed.

Unsuccessful approach: Counting stones after removal but also granting dead stones to their owner as a separate tally still double-books them.

Case contract

Input [board, dead_points, komi_tenths]. Remove dead stones (no prisoners in area scoring), then area = remaining stones + regions bordered by one colour. Return [black, white, margin_tenths].

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, dead, komi = x
    dd = {tuple(p) for p in dead}
    g = [[('.' if (r, c) in dd else ch) for c, ch in enumerate(row)] for r, row in enumerate(board)]
    n = len(g)
    m = len(g[0])
    area = {'B': 0, 'W': 0}
    for row in board:
        for ch in row:
            if ch != '.':
                area[ch] += 1
    seen = set()
    for r in range(n):
        for c in range(m):
            if g[r][c] != '.' or (r, c) in seen:
                continue
            seen.add((r, c))
            todo = [(r, c)]
            cnt = 0
            edge = set()
            while todo:
                i, j = todo.pop()
                cnt += 1
                for p, q in ((i, j + 1), (i, j - 1), (i + 1, j), (i - 1, j)):
                    if 0 <= p < n and 0 <= q < m:
                        if g[p][q] == '.':
                            if (p, q) not in seen:
                                seen.add((p, q))
                                todo.append((p, q))
                        else:
                            edge.add(g[p][q])
            if len(edge) == 1:
                area[edge.pop()] += cnt
    return [area['B'], area['W'], (area['B'] - area['W']) * 10 - komi]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['.BW..', 'WBW..', 'WBW..', '.BW.B', 'WBW..', 'WBW.B'], [[2, 1]], 0], [7, 10, -30]], [[['.BW...', '.BW...', '.BW...', 'WBW...', 'WBW...'], [[4, 2]], 0], [5, 6, -10]], [[['...W.BW', '.....BW', 'W....BW', '.....BW', '..W..BW'], [[1, 6], [2, 6]], 0], [5, 6, -10]], [[['.BWB', '.BW.', '.BWB', '.BW.', '.BWB'], [], 0], [13, 5, 80]], [[['.W.BW..', '...BW..', '...BWB.', '...BW..'], [[1, 4], [3, 4]], 0], [5, 3, 20]], [[['.BWBB.', '.BW...', '.BW..B', '.BW...', '.BW...', '.BW..B', '.BW.B.'], [[3, 1]], 5], [12, 7, 45]], [[['..W..BW', '.....BW', '..W..BW', '.....BW', '.W...BW', '...W.BW'], [], 0], [6, 10, -40]], [[['.BW..', '.BW..', '.BWB.', '.BWB.', 'WBW..'], [[4, 2]], 75], [7, 5, -55]]], [[[['.BW....', '.BWB...', '.BW.B..', 'WBW.B..', '.BW.B..', '.BW....'], [[5, 2]], 0], [10, 6, 40]], [[['..BWB', '..BW.', '..BWB', 'W.BW.', '..BW.'], [[4, 2], [3, 3]], 75], [6, 5, -65]], [[['.BW.', '.BW.', '.BW.', '.BW.', 'WBW.', '.BW.', 'WBW.'], [[2, 2], [6, 2]], 75], [7, 7, -75]], [[['..BW', '..BW', 'W.BW', '..BW'], [[0, 2], [2, 3]], 75], [3, 4, -85]], [[['.W.BW.', '...BW.', '...BW.', '...BW.', '.W.BW.', '...BW.'], [[4, 3], [0, 4]], 0], [5, 7, -20]], [[['W..BW.', '...BW.', '...BWB', '...BW.'], [[3, 4], [0, 3]], 75], [4, 4, -75]], [[['.WBW...', '..BW...', '..BW...', 'W.BW...', '..BW...'], [[0, 3], [4, 2]], 75], [4, 6, -95]], [[['..BW', 'W.BW', '..BW', 'W.BW', '..BW'], [], 75], [5, 7, -95]]], [[[['...W.BW', '.....BW', 'W....BW', '.....BW', '..W..BW'], [[1, 6], [2, 6]], 0], [5, 6, -10]], [[['..BW...', '..BW...', '..BW...', '..BW...', '..BWB..', '.WBW...'], [[5, 3]], 5], [7, 6, 5]], [[['...BW', '.W.BW', 'W..BW', '..WBW'], [[3, 4], [0, 4]], 5], [4, 5, -15]], [[['..BW..B', '..BW...', '..BW...', '..BW...'], [], 75], [13, 4, 15]], [[['..BW', '..BW', '.WBW', '..BW', '..BW', '..BW'], [[1, 2]], 5], [5, 7, -25]], [[['W.BW.', '..BW.', 'W.BW.', '..BW.', '..BW.', '..BW.', '.WBW.'], [[6, 1]], 5], [7, 16, -95]], [[['..BW.', '..BW.', '..BW.', '..BW.', '..BW.', '..BW.'], [], 5], [18, 12, 55]], [[['..BW.', '.WBW.', '..BW.', '.WBW.'], [[2, 2], [3, 2]], 5], [2, 10, -85]]], [[[['.BW..', '.BW..', '.BWB.', '.BWB.', 'WBW..'], [[4, 2]], 75], [7, 5, -55]], [[['.WBWB', '..BW.', '..BW.', '..BW.', '..BW.'], [], 0], [6, 6, 0]], [[['...BW.B', '...BWBB', '...BWBB', '...BW..', '...BW..'], [[3, 3], [0, 4]], 5], [9, 4, 45]], [[['.W..BW.', '..W.BW.', '....BW.', '..W.BW.', '...WBW.', '....BW.', '...WBW.'], [[2, 4], [3, 5]], 75], [6, 11, -125]], [[['...BW', '..WBW', '...BW', '..WBW', '...BW'], [[2, 3]], 75], [4, 7, -105]], [[['.WBW', '..BW', '..BW', '..BW', '..BW', '..BW'], [[2, 2], [4, 2]], 5], [4, 7, -35]], [[['WBW..', '.BW..', '.BW..', '.BWBB'], [[1, 2]], 0], [6, 4, 20]], [[['...WBW.', '....BW.', '....BW.', 'W...BW.', '....BW.', '..W.BW.'], [[1, 4]], 5], [5, 15, -105]]], [[[['..BW', '..BW', 'W.BW', '..BW'], [[0, 2], [2, 3]], 75], [3, 4, -85]], [[['...WBW.', '....BW.', '....BW.', 'W...BW.', '....BW.', '..W.BW.'], [[1, 4]], 5], [5, 15, -105]], [[['...BW', 'W..BW', '..WBW', '...BW', '..WBW', '...BW'], [[5, 3]], 0], [5, 9, -40]], [[['..BW.', '..BW.', '..BW.', '..BW.', 'W.BW.', '..BW.', '..BW.'], [], 5], [7, 15, -85]], [[['.BWB', '.BW.', '.BW.', '.BW.'], [], 0], [9, 4, 50]], [[['....', '....', '....', '....'], [], 75], [0, 0, -75]], [[['...', '...', '...'], [], 5], [0, 0, -5]], [[['.B.', 'B.B', '.B.'], [[2, 1], [1, 2]], 0], [9, 0, 90]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("area with dead 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
area with dead case 0[8, 10, -20][7, 10, -30]Failed
area with dead case 1[5, 7, -20][5, 6, -10]Failed
area with dead case 2[5, 8, -30][5, 6, -10]Failed
area with dead case 3[13, 5, 80][13, 5, 80]Passed
area with dead case 4[5, 5, 0][5, 3, 20]Failed
area with dead case 5[13, 7, 55][12, 7, 45]Failed
area with dead case 6[6, 10, -40][6, 10, -40]Passed
area with dead case 7[7, 6, -65][7, 5, -55]Failed

SHA-256 / 4ec0dfaa9f17e201f28e738d0b02d0981ea93022d20be06680435a6e1e7be17c

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    board, dead, komi = x
    dd = {tuple(p) for p in dead}
    g = [[('.' if (r, c) in dd else ch) for c, ch in enumerate(row)] for r, row in enumerate(board)]
    n = len(g)
    m = len(g[0])
    area = {'B': 0, 'W': 0}
    for row in g:
        for ch in row:
            if ch != '.':
                area[ch] += 1
    for r, c in dd:
        if board[r][c] != '.':
            area[board[r][c]] += 1
    seen = set()
    for r in range(n):
        for c in range(m):
            if g[r][c] != '.' or (r, c) in seen:
                continue
            seen.add((r, c))
            todo = [(r, c)]
            cnt = 0
            edge = set()
            while todo:
                i, j = todo.pop()
                cnt += 1
                for p, q in ((i, j + 1), (i, j - 1), (i + 1, j), (i - 1, j)):
                    if 0 <= p < n and 0 <= q < m:
                        if g[p][q] == '.':
                            if (p, q) not in seen:
                                seen.add((p, q))
                                todo.append((p, q))
                        else:
                            edge.add(g[p][q])
            if len(edge) == 1:
                area[edge.pop()] += cnt
    return [area['B'], area['W'], (area['B'] - area['W']) * 10 - komi]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['.BW..', 'WBW..', 'WBW..', '.BW.B', 'WBW..', 'WBW.B'], [[2, 1]], 0], [7, 10, -30]], [[['.BW...', '.BW...', '.BW...', 'WBW...', 'WBW...'], [[4, 2]], 0], [5, 6, -10]], [[['...W.BW', '.....BW', 'W....BW', '.....BW', '..W..BW'], [[1, 6], [2, 6]], 0], [5, 6, -10]], [[['.BWB', '.BW.', '.BWB', '.BW.', '.BWB'], [], 0], [13, 5, 80]], [[['.W.BW..', '...BW..', '...BWB.', '...BW..'], [[1, 4], [3, 4]], 0], [5, 3, 20]], [[['.BWBB.', '.BW...', '.BW..B', '.BW...', '.BW...', '.BW..B', '.BW.B.'], [[3, 1]], 5], [12, 7, 45]], [[['..W..BW', '.....BW', '..W..BW', '.....BW', '.W...BW', '...W.BW'], [], 0], [6, 10, -40]], [[['.BW..', '.BW..', '.BWB.', '.BWB.', 'WBW..'], [[4, 2]], 75], [7, 5, -55]]], [[[['.BW....', '.BWB...', '.BW.B..', 'WBW.B..', '.BW.B..', '.BW....'], [[5, 2]], 0], [10, 6, 40]], [[['..BWB', '..BW.', '..BWB', 'W.BW.', '..BW.'], [[4, 2], [3, 3]], 75], [6, 5, -65]], [[['.BW.', '.BW.', '.BW.', '.BW.', 'WBW.', '.BW.', 'WBW.'], [[2, 2], [6, 2]], 75], [7, 7, -75]], [[['..BW', '..BW', 'W.BW', '..BW'], [[0, 2], [2, 3]], 75], [3, 4, -85]], [[['.W.BW.', '...BW.', '...BW.', '...BW.', '.W.BW.', '...BW.'], [[4, 3], [0, 4]], 0], [5, 7, -20]], [[['W..BW.', '...BW.', '...BWB', '...BW.'], [[3, 4], [0, 3]], 75], [4, 4, -75]], [[['.WBW...', '..BW...', '..BW...', 'W.BW...', '..BW...'], [[0, 3], [4, 2]], 75], [4, 6, -95]], [[['..BW', 'W.BW', '..BW', 'W.BW', '..BW'], [], 75], [5, 7, -95]]], [[[['...W.BW', '.....BW', 'W....BW', '.....BW', '..W..BW'], [[1, 6], [2, 6]], 0], [5, 6, -10]], [[['..BW...', '..BW...', '..BW...', '..BW...', '..BWB..', '.WBW...'], [[5, 3]], 5], [7, 6, 5]], [[['...BW', '.W.BW', 'W..BW', '..WBW'], [[3, 4], [0, 4]], 5], [4, 5, -15]], [[['..BW..B', '..BW...', '..BW...', '..BW...'], [], 75], [13, 4, 15]], [[['..BW', '..BW', '.WBW', '..BW', '..BW', '..BW'], [[1, 2]], 5], [5, 7, -25]], [[['W.BW.', '..BW.', 'W.BW.', '..BW.', '..BW.', '..BW.', '.WBW.'], [[6, 1]], 5], [7, 16, -95]], [[['..BW.', '..BW.', '..BW.', '..BW.', '..BW.', '..BW.'], [], 5], [18, 12, 55]], [[['..BW.', '.WBW.', '..BW.', '.WBW.'], [[2, 2], [3, 2]], 5], [2, 10, -85]]], [[[['.BW..', '.BW..', '.BWB.', '.BWB.', 'WBW..'], [[4, 2]], 75], [7, 5, -55]], [[['.WBWB', '..BW.', '..BW.', '..BW.', '..BW.'], [], 0], [6, 6, 0]], [[['...BW.B', '...BWBB', '...BWBB', '...BW..', '...BW..'], [[3, 3], [0, 4]], 5], [9, 4, 45]], [[['.W..BW.', '..W.BW.', '....BW.', '..W.BW.', '...WBW.', '....BW.', '...WBW.'], [[2, 4], [3, 5]], 75], [6, 11, -125]], [[['...BW', '..WBW', '...BW', '..WBW', '...BW'], [[2, 3]], 75], [4, 7, -105]], [[['.WBW', '..BW', '..BW', '..BW', '..BW', '..BW'], [[2, 2], [4, 2]], 5], [4, 7, -35]], [[['WBW..', '.BW..', '.BW..', '.BWBB'], [[1, 2]], 0], [6, 4, 20]], [[['...WBW.', '....BW.', '....BW.', 'W...BW.', '....BW.', '..W.BW.'], [[1, 4]], 5], [5, 15, -105]]], [[[['..BW', '..BW', 'W.BW', '..BW'], [[0, 2], [2, 3]], 75], [3, 4, -85]], [[['...WBW.', '....BW.', '....BW.', 'W...BW.', '....BW.', '..W.BW.'], [[1, 4]], 5], [5, 15, -105]], [[['...BW', 'W..BW', '..WBW', '...BW', '..WBW', '...BW'], [[5, 3]], 0], [5, 9, -40]], [[['..BW.', '..BW.', '..BW.', '..BW.', 'W.BW.', '..BW.', '..BW.'], [], 5], [7, 15, -85]], [[['.BWB', '.BW.', '.BW.', '.BW.'], [], 0], [9, 4, 50]], [[['....', '....', '....', '....'], [], 75], [0, 0, -75]], [[['...', '...', '...'], [], 5], [0, 0, -5]], [[['.B.', 'B.B', '.B.'], [[2, 1], [1, 2]], 0], [9, 0, 90]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("area with dead 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
area with dead case 0[8, 10, -20][7, 10, -30]Failed
area with dead case 1[5, 7, -20][5, 6, -10]Failed
area with dead case 2[5, 8, -30][5, 6, -10]Failed
area with dead case 3[13, 5, 80][13, 5, 80]Passed
area with dead case 4[5, 5, 0][5, 3, 20]Failed
area with dead case 5[13, 7, 55][12, 7, 45]Failed
area with dead case 6[6, 10, -40][6, 10, -40]Passed
area with dead case 7[7, 6, -65][7, 5, -55]Failed

SHA-256 / 207ad7b99f9075c347c4184319fef9b6c81fa21060973605e1fd2f223014e591

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    board, dead, komi = x
    dd = {tuple(p) for p in dead}
    g = [[('.' if (r, c) in dd else ch) for c, ch in enumerate(row)] for r, row in enumerate(board)]
    n = len(g)
    m = len(g[0])
    area = {'B': 0, 'W': 0}
    for row in g:
        for ch in row:
            if ch != '.':
                area[ch] += 1
    seen = set()
    for r in range(n):
        for c in range(m):
            if g[r][c] != '.' or (r, c) in seen:
                continue
            seen.add((r, c))
            todo = [(r, c)]
            cnt = 0
            edge = set()
            while todo:
                i, j = todo.pop()
                cnt += 1
                for p, q in ((i, j + 1), (i, j - 1), (i + 1, j), (i - 1, j)):
                    if 0 <= p < n and 0 <= q < m:
                        if g[p][q] == '.':
                            if (p, q) not in seen:
                                seen.add((p, q))
                                todo.append((p, q))
                        else:
                            edge.add(g[p][q])
            if len(edge) == 1:
                area[edge.pop()] += cnt
    return [area['B'], area['W'], (area['B'] - area['W']) * 10 - komi]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[['.BW..', 'WBW..', 'WBW..', '.BW.B', 'WBW..', 'WBW.B'], [[2, 1]], 0], [7, 10, -30]], [[['.BW...', '.BW...', '.BW...', 'WBW...', 'WBW...'], [[4, 2]], 0], [5, 6, -10]], [[['...W.BW', '.....BW', 'W....BW', '.....BW', '..W..BW'], [[1, 6], [2, 6]], 0], [5, 6, -10]], [[['.BWB', '.BW.', '.BWB', '.BW.', '.BWB'], [], 0], [13, 5, 80]], [[['.W.BW..', '...BW..', '...BWB.', '...BW..'], [[1, 4], [3, 4]], 0], [5, 3, 20]], [[['.BWBB.', '.BW...', '.BW..B', '.BW...', '.BW...', '.BW..B', '.BW.B.'], [[3, 1]], 5], [12, 7, 45]], [[['..W..BW', '.....BW', '..W..BW', '.....BW', '.W...BW', '...W.BW'], [], 0], [6, 10, -40]], [[['.BW..', '.BW..', '.BWB.', '.BWB.', 'WBW..'], [[4, 2]], 75], [7, 5, -55]]], [[[['.BW....', '.BWB...', '.BW.B..', 'WBW.B..', '.BW.B..', '.BW....'], [[5, 2]], 0], [10, 6, 40]], [[['..BWB', '..BW.', '..BWB', 'W.BW.', '..BW.'], [[4, 2], [3, 3]], 75], [6, 5, -65]], [[['.BW.', '.BW.', '.BW.', '.BW.', 'WBW.', '.BW.', 'WBW.'], [[2, 2], [6, 2]], 75], [7, 7, -75]], [[['..BW', '..BW', 'W.BW', '..BW'], [[0, 2], [2, 3]], 75], [3, 4, -85]], [[['.W.BW.', '...BW.', '...BW.', '...BW.', '.W.BW.', '...BW.'], [[4, 3], [0, 4]], 0], [5, 7, -20]], [[['W..BW.', '...BW.', '...BWB', '...BW.'], [[3, 4], [0, 3]], 75], [4, 4, -75]], [[['.WBW...', '..BW...', '..BW...', 'W.BW...', '..BW...'], [[0, 3], [4, 2]], 75], [4, 6, -95]], [[['..BW', 'W.BW', '..BW', 'W.BW', '..BW'], [], 75], [5, 7, -95]]], [[[['...W.BW', '.....BW', 'W....BW', '.....BW', '..W..BW'], [[1, 6], [2, 6]], 0], [5, 6, -10]], [[['..BW...', '..BW...', '..BW...', '..BW...', '..BWB..', '.WBW...'], [[5, 3]], 5], [7, 6, 5]], [[['...BW', '.W.BW', 'W..BW', '..WBW'], [[3, 4], [0, 4]], 5], [4, 5, -15]], [[['..BW..B', '..BW...', '..BW...', '..BW...'], [], 75], [13, 4, 15]], [[['..BW', '..BW', '.WBW', '..BW', '..BW', '..BW'], [[1, 2]], 5], [5, 7, -25]], [[['W.BW.', '..BW.', 'W.BW.', '..BW.', '..BW.', '..BW.', '.WBW.'], [[6, 1]], 5], [7, 16, -95]], [[['..BW.', '..BW.', '..BW.', '..BW.', '..BW.', '..BW.'], [], 5], [18, 12, 55]], [[['..BW.', '.WBW.', '..BW.', '.WBW.'], [[2, 2], [3, 2]], 5], [2, 10, -85]]], [[[['.BW..', '.BW..', '.BWB.', '.BWB.', 'WBW..'], [[4, 2]], 75], [7, 5, -55]], [[['.WBWB', '..BW.', '..BW.', '..BW.', '..BW.'], [], 0], [6, 6, 0]], [[['...BW.B', '...BWBB', '...BWBB', '...BW..', '...BW..'], [[3, 3], [0, 4]], 5], [9, 4, 45]], [[['.W..BW.', '..W.BW.', '....BW.', '..W.BW.', '...WBW.', '....BW.', '...WBW.'], [[2, 4], [3, 5]], 75], [6, 11, -125]], [[['...BW', '..WBW', '...BW', '..WBW', '...BW'], [[2, 3]], 75], [4, 7, -105]], [[['.WBW', '..BW', '..BW', '..BW', '..BW', '..BW'], [[2, 2], [4, 2]], 5], [4, 7, -35]], [[['WBW..', '.BW..', '.BW..', '.BWBB'], [[1, 2]], 0], [6, 4, 20]], [[['...WBW.', '....BW.', '....BW.', 'W...BW.', '....BW.', '..W.BW.'], [[1, 4]], 5], [5, 15, -105]]], [[[['..BW', '..BW', 'W.BW', '..BW'], [[0, 2], [2, 3]], 75], [3, 4, -85]], [[['...WBW.', '....BW.', '....BW.', 'W...BW.', '....BW.', '..W.BW.'], [[1, 4]], 5], [5, 15, -105]], [[['...BW', 'W..BW', '..WBW', '...BW', '..WBW', '...BW'], [[5, 3]], 0], [5, 9, -40]], [[['..BW.', '..BW.', '..BW.', '..BW.', 'W.BW.', '..BW.', '..BW.'], [], 5], [7, 15, -85]], [[['.BWB', '.BW.', '.BW.', '.BW.'], [], 0], [9, 4, 50]], [[['....', '....', '....', '....'], [], 75], [0, 0, -75]], [[['...', '...', '...'], [], 5], [0, 0, -5]], [[['.B.', 'B.B', '.B.'], [[2, 1], [1, 2]], 0], [9, 0, 90]]]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("area with dead 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
area with dead case 0[7, 10, -30][7, 10, -30]Passed
area with dead case 1[5, 6, -10][5, 6, -10]Passed
area with dead case 2[5, 6, -10][5, 6, -10]Passed
area with dead case 3[13, 5, 80][13, 5, 80]Passed
area with dead case 4[5, 3, 20][5, 3, 20]Passed
area with dead case 5[12, 7, 45][12, 7, 45]Passed
area with dead case 6[6, 10, -40][6, 10, -40]Passed
area with dead case 7[7, 5, -55][7, 5, -55]Passed

SHA-256 / ac45b5d88e23cecaab6ca9c3b9e882373f13e48e172453c39b0efd6873985837

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:20.659567+00:00.

Case digest / b7d95263e68b7036258778f81ee457f190d577d8b7bfa7fab15cc32825fc38bd