FAILURE MAP
← Case archive

FA-88681 / Mesh topology invariants / Open access

One-ring walk stops before leaving the start · case 01

Every ring collapses to its start vertex and is reported non-manifold.

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

ROOT CAUSE

The loop condition compares the current vertex, not its successor, with the start.

THE FAILURE

The loop condition compares the current vertex, not its successor, with the start.

Unsuccessful approach: Capping the walk at the successor count truncates boundary fans.

Case contract

Input [faces, v] with consistently oriented polygons. Each face containing v maps its next corner p to its previous corner q (counter-clockwise order around v). A repeated p is "nonmanifold"; no incident face is "isolated". If exactly one p has no predecessor the ring is a boundary fan starting there; more than one is "nonmanifold"; otherwise start at the smallest neighbour. Walk successors; a walk that does not visit every neighbour is "nonmanifold". Return [ring, "boundary"|"interior"].

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,v=x
    succ={}
    for f in faces:
        n=len(f)
        for j in range(n):
            if f[j]==v:
                p,q=f[(j+1)%n],f[(j-1)%n]
                if p in succ: return 'nonmanifold'
                succ[p]=q
    if not succ: return 'isolated'
    starts=[p for p in succ if p not in succ.values()]
    if len(starts)>1: return 'nonmanifold'
    s=starts[0] if starts else min(succ)
    ring=[s]
    u=s
    while u in succ and u!=s:
        u=succ[u]
        ring.append(u)
    if len(ring)!=len(set(succ)|set(succ.values())): return 'nonmanifold'
    return [ring,'boundary' if starts else 'interior']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk centre', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0]], [[1, 2, 3, 4, 5], 'interior']], ['disk rim vertex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 2]], [[3, 0, 1], 'boundary']], ['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['fan middle rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 3]], [[4, 0, 2], 'boundary']], ['octahedron top', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 0]], [[1, 2, 3, 4], 'interior']], ['octahedron equator', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 2]], [[0, 1, 5, 3], 'interior']], ['bowtie pinch', [[[[0, 1, 2], [0, 3, 4]], 0]], 'nonmanifold']], [['disk rim vertex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 2]], [[3, 0, 1], 'boundary']], ['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['octahedron equator', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 2]], [[0, 1, 5, 3], 'interior']], ['bowtie pinch', [[[[0, 1, 2], [0, 3, 4]], 0]], 'nonmanifold'], ['double disk pinch', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [0, 6, 7], [0, 7, 8], [0, 8, 6]], 0]], 'nonmanifold'], ['quad grid centre', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 4]], [[1, 5, 7, 3], 'interior']], ['quad grid edge', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 1]], [[2, 4, 0], 'boundary']]], [['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['fan middle rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 3]], [[4, 0, 2], 'boundary']], ['quad grid centre', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 4]], [[1, 5, 7, 3], 'interior']], ['quad grid edge', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 1]], [[2, 4, 0], 'boundary']], ['isolated vertex', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 9]], 'isolated'], ['flipped neighbour', [[[[0, 1, 2], [0, 3, 2]], 0]], 'nonmanifold'], ['same spoke twice', [[[[0, 1, 2], [0, 1, 3]], 0]], 'nonmanifold']], [['fan middle rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 3]], [[4, 0, 2], 'boundary']], ['double disk pinch', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [0, 6, 7], [0, 7, 8], [0, 8, 6]], 0]], 'nonmanifold'], ['flipped neighbour', [[[[0, 1, 2], [0, 3, 2]], 0]], 'nonmanifold'], ['same spoke twice', [[[[0, 1, 2], [0, 1, 3]], 0]], 'nonmanifold'], ['duplicated face', [[[[0, 1, 2], [0, 1, 2], [0, 2, 3]], 0]], 'nonmanifold'], ['cube corner', [[[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]], 6]], [[2, 7, 5], 'interior']], ['torus vertex', [[[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]], 4]], [[0, 3, 7, 8, 5, 1], 'interior']]], [['disk centre', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0]], [[1, 2, 3, 4, 5], 'interior']], ['disk rim vertex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 2]], [[3, 0, 1], 'boundary']], ['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['octahedron top', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 0]], [[1, 2, 3, 4], 'interior']], ['quad grid edge', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 1]], [[2, 4, 0], 'boundary']], ['cube corner', [[[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]], 6]], [[2, 7, 5], 'interior']], ['torus vertex', [[[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]], 4]], [[0, 3, 7, 8, 5, 1], 'interior']]]]
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 centrenonmanifold[[1, 2, 3, 4, 5], 'interior']Failed
disk rim vertexnonmanifold[[3, 0, 1], 'boundary']Failed
fan apexnonmanifold[[1, 2, 3, 4], 'boundary']Failed
fan middle rimnonmanifold[[4, 0, 2], 'boundary']Failed
octahedron topnonmanifold[[1, 2, 3, 4], 'interior']Failed
octahedron equatornonmanifold[[0, 1, 5, 3], 'interior']Failed
bowtie pinchnonmanifoldnonmanifoldPassed

SHA-256 / da97b99e70c4fd95e23c1e406d61f03c188a93c95ef679ff96ffe85afe1fecea

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,v=x
    succ={}
    for f in faces:
        n=len(f)
        for j in range(n):
            if f[j]==v:
                p,q=f[(j+1)%n],f[(j-1)%n]
                if p in succ: return 'nonmanifold'
                succ[p]=q
    if not succ: return 'isolated'
    starts=[p for p in succ if p not in succ.values()]
    if len(starts)>1: return 'nonmanifold'
    s=starts[0] if starts else min(succ)
    ring=[s]
    u=s
    while u in succ and len(ring)<len(succ):
        u=succ[u]
        ring.append(u)
    if len(ring)!=len(set(succ)|set(succ.values())): return 'nonmanifold'
    return [ring,'boundary' if starts else 'interior']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk centre', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0]], [[1, 2, 3, 4, 5], 'interior']], ['disk rim vertex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 2]], [[3, 0, 1], 'boundary']], ['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['fan middle rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 3]], [[4, 0, 2], 'boundary']], ['octahedron top', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 0]], [[1, 2, 3, 4], 'interior']], ['octahedron equator', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 2]], [[0, 1, 5, 3], 'interior']], ['bowtie pinch', [[[[0, 1, 2], [0, 3, 4]], 0]], 'nonmanifold']], [['disk rim vertex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 2]], [[3, 0, 1], 'boundary']], ['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['octahedron equator', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 2]], [[0, 1, 5, 3], 'interior']], ['bowtie pinch', [[[[0, 1, 2], [0, 3, 4]], 0]], 'nonmanifold'], ['double disk pinch', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [0, 6, 7], [0, 7, 8], [0, 8, 6]], 0]], 'nonmanifold'], ['quad grid centre', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 4]], [[1, 5, 7, 3], 'interior']], ['quad grid edge', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 1]], [[2, 4, 0], 'boundary']]], [['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['fan middle rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 3]], [[4, 0, 2], 'boundary']], ['quad grid centre', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 4]], [[1, 5, 7, 3], 'interior']], ['quad grid edge', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 1]], [[2, 4, 0], 'boundary']], ['isolated vertex', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 9]], 'isolated'], ['flipped neighbour', [[[[0, 1, 2], [0, 3, 2]], 0]], 'nonmanifold'], ['same spoke twice', [[[[0, 1, 2], [0, 1, 3]], 0]], 'nonmanifold']], [['fan middle rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 3]], [[4, 0, 2], 'boundary']], ['double disk pinch', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1], [0, 6, 7], [0, 7, 8], [0, 8, 6]], 0]], 'nonmanifold'], ['flipped neighbour', [[[[0, 1, 2], [0, 3, 2]], 0]], 'nonmanifold'], ['same spoke twice', [[[[0, 1, 2], [0, 1, 3]], 0]], 'nonmanifold'], ['duplicated face', [[[[0, 1, 2], [0, 1, 2], [0, 2, 3]], 0]], 'nonmanifold'], ['cube corner', [[[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]], 6]], [[2, 7, 5], 'interior']], ['torus vertex', [[[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]], 4]], [[0, 3, 7, 8, 5, 1], 'interior']]], [['disk centre', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0]], [[1, 2, 3, 4, 5], 'interior']], ['disk rim vertex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 2]], [[3, 0, 1], 'boundary']], ['fan apex', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0]], [[1, 2, 3, 4], 'boundary']], ['octahedron top', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 0]], [[1, 2, 3, 4], 'interior']], ['quad grid edge', [[[[0, 1, 4, 3], [1, 2, 5, 4], [3, 4, 7, 6], [4, 5, 8, 7]], 1]], [[2, 4, 0], 'boundary']], ['cube corner', [[[[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]], 6]], [[2, 7, 5], 'interior']], ['torus vertex', [[[[0, 3, 4], [0, 4, 1], [1, 4, 5], [1, 5, 2], [2, 5, 3], [2, 3, 0], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 5], [5, 8, 6], [5, 6, 3], [6, 0, 1], [6, 1, 7], [7, 1, 2], [7, 2, 8], [8, 2, 0], [8, 0, 6]], 4]], [[0, 3, 7, 8, 5, 1], 'interior']]]]
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 centre[[1, 2, 3, 4, 5], 'interior'][[1, 2, 3, 4, 5], 'interior']Passed
disk rim vertexnonmanifold[[3, 0, 1], 'boundary']Failed
fan apexnonmanifold[[1, 2, 3, 4], 'boundary']Failed
fan middle rimnonmanifold[[4, 0, 2], 'boundary']Failed
octahedron top[[1, 2, 3, 4], 'interior'][[1, 2, 3, 4], 'interior']Passed
octahedron equator[[0, 1, 5, 3], 'interior'][[0, 1, 5, 3], 'interior']Passed
bowtie pinchnonmanifoldnonmanifoldPassed

SHA-256 / 8022deacf67e1fff9791f1ad0c9ca02416ce74ed62bc14a9a8eef913f41d2c19

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 7 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

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

Case digest / ee890c9ce4ce790783b8608216dfc5da138d73d792474c1965069da343311325