FAILURE MAP
← Case archive

FA-88411 / Mesh topology invariants / Open access

Boundary loop walk starts from an arbitrary vertex · case 01

Loops start at whichever vertex was inserted first rather than at their smallest vertex.

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

ROOT CAUSE

The walk iterates successor keys in insertion order.

VERIFIED REPAIR

Start each loop at the smallest unvisited boundary vertex.

Unsuccessful approach: Iterating in descending order starts each loop at its largest 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[a]=b
    loops=[]
    seen=set()
    for s in 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 triangle', [[[4, 2, 7]]], [[2, 7, 4]]], ['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]]]], [['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'], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]]]]
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[[1, 0, 2], [3, 4, 5]][[0, 2, 1], [3, 4, 5]]Failed
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], [5, 4, 7, 6]][[0, 1, 2, 3], [4, 7, 6, 5]]Failed
closed tetrahedron[][]Passed
bowtieambiguousambiguousPassed

SHA-256 / bddfd08dabfc4c7ce0ec43c161daf6cc7141a9f36b19d96f88b8a77fd3cb9288

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,reverse=True):
        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 triangle', [[[4, 2, 7]]], [[2, 7, 4]]], ['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]]]], [['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'], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]]]]
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, 1, 2, 3, 4]][[1, 2, 3, 4, 5]]Failed
fan[[4, 0, 1, 2, 3]][[0, 1, 2, 3, 4]]Failed
annulus[[2, 1, 0], [5, 3, 4]][[0, 2, 1], [3, 4, 5]]Failed
quad grid[[8, 7, 6, 3, 0, 1, 2, 5]][[0, 1, 2, 5, 8, 7, 6, 3]]Failed
square with hole[[3, 0, 1, 2], [7, 6, 5, 4]][[0, 1, 2, 3], [4, 7, 6, 5]]Failed
closed tetrahedron[][]Passed
bowtieambiguousambiguousPassed

SHA-256 / 8df670a91dfe7537992205d091953e356bb28cea39a2251d08d757cb1229895d

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 triangle', [[[4, 2, 7]]], [[2, 7, 4]]], ['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]]]], [['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'], ['single quad', [[[3, 1, 0, 2]]], [[0, 2, 3, 1]]]]]
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 / 126c47876a5a69168bc3fe93a2bdc8cc58109b0b333cd646e2d9345766352b09

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

Case digest / f3fc9bdebf5d1d589dc2eb9aa457265600dd6c44614e4af1108f865c76619663