FAILURE MAP
← Case archive

FA-88396 / Mesh topology invariants / Open access

Boundary loops are walked against the face orientation · case 01

Every loop comes back in reversed vertex order.

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

ROOT CAUSE

The successor map stores b -> a for a boundary half-edge a -> b.

VERIFIED REPAIR

Map each boundary half-edge start to its end.

Unsuccessful approach: Reversing the finished loop keeps the wrong order and no longer starts at the smallest vertex.

Case contract

Input: consistently oriented polygon faces. A half-edge (a,b) is boundary when (b,a) is not a half-edge. If any vertex starts two boundary half-edges return "ambiguous". Otherwise walk loops along the half-edge direction, each starting at its smallest vertex, and return them sorted by descending length then first vertex.

Why this case matters

Mesh processing pipelines (remeshing, export, simulation, printing) trust these topological counts and adjacency answers; a wrong invariant silently accepts broken meshes or rejects valid ones.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    he=[]
    for f in faces:
        n=len(f)
        for i in range(n):
            he.append((f[i],f[(i+1)%n]))
    hs=set(he)
    nxt={}
    for a,b in he:
        if (b,a) not in hs:
            if a in nxt: return 'ambiguous'
            nxt[b]=a
    loops=[]
    seen=set()
    for s in sorted(nxt):
        if s in seen: continue
        loop=[]
        v=s
        while v not in seen:
            seen.add(v)
            loop.append(v)
            v=nxt[v]
        loops.append(loop)
    return sorted(loops,key=lambda l:(-len(l),l[0]))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous']], [['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous'], ['two disks', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [10, 11, 12], [10, 12, 13], [10, 13, 14]]], [[1, 2, 3, 4, 5], [10, 11, 12, 13, 14]]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['single triangle', [[[4, 2, 7]]], [[2, 7, 4]]]], [['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['two disks', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [10, 11, 12], [10, 12, 13], [10, 13, 14]]], [[1, 2, 3, 4, 5], [10, 11, 12, 13, 14]]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['single triangle', [[[4, 2, 7]]], [[2, 7, 4]]], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]], ['triangle grid', [[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]]], [[0, 1, 2, 5, 4, 3]]], ['disk with small far island', [[[5, 6, 7], [5, 7, 8], [5, 8, 9], [5, 9, 10], [5, 10, 6], [0, 1, 2]]], [[6, 7, 8, 9, 10], [0, 1, 2]]]], [['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]], ['triangle grid', [[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]]], [[0, 1, 2, 5, 4, 3]]], ['disk with small far island', [[[5, 6, 7], [5, 7, 8], [5, 8, 9], [5, 9, 10], [5, 10, 6], [0, 1, 2]]], [[6, 7, 8, 9, 10], [0, 1, 2]]]], [['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous']]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
diskambiguous[[1, 2, 3, 4, 5]]Failed
fanambiguous[[0, 1, 2, 3, 4]]Failed
annulusambiguous[[0, 2, 1], [3, 4, 5]]Failed
quad gridambiguous[[0, 1, 2, 5, 8, 7, 6, 3]]Failed
square with holeambiguous[[0, 1, 2, 3], [4, 7, 6, 5]]Failed
closed tetrahedron[][]Passed
bowtieambiguousambiguousPassed

SHA-256 / 0592c797d29a9227ca5432988ea49f398e86cf8669d98948d7c6f6fb1cd84e84

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    he=[]
    for f in faces:
        n=len(f)
        for i in range(n):
            he.append((f[i],f[(i+1)%n]))
    hs=set(he)
    nxt={}
    for a,b in he:
        if (b,a) not in hs:
            if a in nxt: return 'ambiguous'
            nxt[a]=b
    loops=[]
    seen=set()
    for s in sorted(nxt):
        if s in seen: continue
        loop=[]
        v=s
        while v not in seen:
            seen.add(v)
            loop.append(v)
            v=nxt[v]
        loops.append(loop[::-1])
    return sorted(loops,key=lambda l:(-len(l),l[0]))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous']], [['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous'], ['two disks', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [10, 11, 12], [10, 12, 13], [10, 13, 14]]], [[1, 2, 3, 4, 5], [10, 11, 12, 13, 14]]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['single triangle', [[[4, 2, 7]]], [[2, 7, 4]]]], [['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['two disks', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [10, 11, 12], [10, 12, 13], [10, 13, 14]]], [[1, 2, 3, 4, 5], [10, 11, 12, 13, 14]]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['single triangle', [[[4, 2, 7]]], [[2, 7, 4]]], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]], ['triangle grid', [[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]]], [[0, 1, 2, 5, 4, 3]]], ['disk with small far island', [[[5, 6, 7], [5, 7, 8], [5, 8, 9], [5, 9, 10], [5, 10, 6], [0, 1, 2]]], [[6, 7, 8, 9, 10], [0, 1, 2]]]], [['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]], ['triangle grid', [[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]]], [[0, 1, 2, 5, 4, 3]]], ['disk with small far island', [[[5, 6, 7], [5, 7, 8], [5, 8, 9], [5, 9, 10], [5, 10, 6], [0, 1, 2]]], [[6, 7, 8, 9, 10], [0, 1, 2]]]], [['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous']]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
disk[[5, 4, 3, 2, 1]][[1, 2, 3, 4, 5]]Failed
fan[[4, 3, 2, 1, 0]][[0, 1, 2, 3, 4]]Failed
annulus[[1, 2, 0], [5, 4, 3]][[0, 2, 1], [3, 4, 5]]Failed
quad grid[[3, 6, 7, 8, 5, 2, 1, 0]][[0, 1, 2, 5, 8, 7, 6, 3]]Failed
square with hole[[3, 2, 1, 0], [5, 6, 7, 4]][[0, 1, 2, 3], [4, 7, 6, 5]]Failed
closed tetrahedron[][]Passed
bowtieambiguousambiguousPassed

SHA-256 / 69a45d311f4177a88e280462dfc4a12668a4bb6f0010f49bbc8e8def425466b4

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    faces=x
    he=[]
    for f in faces:
        n=len(f)
        for i in range(n):
            he.append((f[i],f[(i+1)%n]))
    hs=set(he)
    nxt={}
    for a,b in he:
        if (b,a) not in hs:
            if a in nxt: return 'ambiguous'
            nxt[a]=b
    loops=[]
    seen=set()
    for s in sorted(nxt):
        if s in seen: continue
        loop=[]
        v=s
        while v not in seen:
            seen.add(v)
            loop.append(v)
            v=nxt[v]
        loops.append(loop)
    return sorted(loops,key=lambda l:(-len(l),l[0]))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous']], [['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous'], ['two disks', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [10, 11, 12], [10, 12, 13], [10, 13, 14]]], [[1, 2, 3, 4, 5], [10, 11, 12, 13, 14]]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['single triangle', [[[4, 2, 7]]], [[2, 7, 4]]]], [['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['two disks', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [10, 11, 12], [10, 12, 13], [10, 13, 14]]], [[1, 2, 3, 4, 5], [10, 11, 12, 13, 14]]], ['open cylinder', [[[0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['single triangle', [[[4, 2, 7]]], [[2, 7, 4]]], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]], ['triangle grid', [[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]]], [[0, 1, 2, 5, 4, 3]]], ['disk with small far island', [[[5, 6, 7], [5, 7, 8], [5, 8, 9], [5, 9, 10], [5, 10, 6], [0, 1, 2]]], [[6, 7, 8, 9, 10], [0, 1, 2]]]], [['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]], ['triangle grid', [[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]]], [[0, 1, 2, 5, 4, 3]]], ['disk with small far island', [[[5, 6, 7], [5, 7, 8], [5, 8, 9], [5, 9, 10], [5, 10, 6], [0, 1, 2]]], [[6, 7, 8, 9, 10], [0, 1, 2]]]], [['disk', [[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]]], [[1, 2, 3, 4, 5]]], ['fan', [[[0, 1, 2], [0, 2, 3], [0, 3, 4]]], [[0, 1, 2, 3, 4]]], ['annulus', [[[0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]], [[0, 2, 1], [3, 4, 5]]], ['quad grid', [[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]]], [[0, 1, 2, 5, 8, 7, 6, 3]]], ['square with hole', [[[0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]], [[0, 1, 2, 3], [4, 7, 6, 5]]], ['closed tetrahedron', [[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]]], []], ['bowtie', [[[0, 1, 2], [0, 3, 4]]], 'ambiguous']]]
for label, args, expected in fixtures[N-1]:
    check(label, 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
disk[[1, 2, 3, 4, 5]][[1, 2, 3, 4, 5]]Passed
fan[[0, 1, 2, 3, 4]][[0, 1, 2, 3, 4]]Passed
annulus[[0, 2, 1], [3, 4, 5]][[0, 2, 1], [3, 4, 5]]Passed
quad grid[[0, 1, 2, 5, 8, 7, 6, 3]][[0, 1, 2, 5, 8, 7, 6, 3]]Passed
square with hole[[0, 1, 2, 3], [4, 7, 6, 5]][[0, 1, 2, 3], [4, 7, 6, 5]]Passed
closed tetrahedron[][]Passed
bowtieambiguousambiguousPassed

SHA-256 / 6ffc8e4c311028403e2a998ab4916a1d31e7ad4d100d70ce3df5f27964750995

Verification & scope

Pure combinatorial teaching model over integer face lists with a stipulated contract; no geometry kernel or file format is implied. 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:51:07.732432+00:00.

Case digest / 2116d007abedd5d941e537411043e91bb0639499d0a5f7d58f46f328032b9216