FAILURE MAP
← Case archive

FA-88496 / Mesh topology invariants / Open access

Tetrahedron edge collapse is allowed · case 01

Collapsing any edge of a tetrahedron is accepted and produces a degenerate surface.

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

ROOT CAUSE

The minimal-mesh test uses < 4 vertices instead of <= 4.

VERIFIED REPAIR

Refuse collapses on closed meshes with 4 or fewer vertices.

Unsuccessful approach: Dropping the closed-mesh condition refuses collapses on small open patches.

Case contract

Input [faces, a, b] for a triangle mesh. Collapsing edge (a,b) is checked in order: missing edge -> [false,"no-edge"]; common neighbours of a and b must equal the opposite vertices of the triangles on the edge -> else [false,"link"]; if a and b are both boundary vertices the edge itself must be a boundary edge -> else [false,"boundary"]; a closed mesh with 4 or fewer vertices -> [false,"minimal"]; otherwise [true,"ok"].

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,a,b=x
    adj={}
    opp=[]
    ecount={}
    for f in faces:
        for i in range(3):
            u,v=f[i],f[(i+1)%3]
            adj.setdefault(u,set()).add(v)
            adj.setdefault(v,set()).add(u)
            k=(min(u,v),max(u,v))
            ecount[k]=ecount.get(k,0)+1
        if a in f and b in f:
            opp.append([w for w in f if w!=a and w!=b][0])
    k=(min(a,b),max(a,b))
    if k not in ecount: return [False,'no-edge']
    if (adj[a]&adj[b])!=set(opp): return [False,'link']
    bnd=set()
    for (u,v),c in ecount.items():
        if c==1: bnd.update((u,v))
    if a in bnd and b in bnd and ecount[k]!=1: return [False,'boundary']
    if not bnd and len(adj)<4: return [False,'minimal']
    return [True,'ok']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk spoke', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0, 1]], [True, 'ok']], ['disk rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 1, 2]], [True, 'ok']], ['fan interior edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0, 2]], [False, 'boundary']], ['fan missing edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 1, 3]], [False, 'no-edge']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['octahedron edge', [[[[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]], [True, 'ok']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']]], [['fan missing edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 1, 3]], [False, 'no-edge']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['octahedron edge', [[[[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]], [True, 'ok']], ['octahedron reversed', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 5, 2]], [True, 'ok']], ['open tetra cap', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1]], 1, 2]], [False, 'link']], ['two triangles shared edge', [[[[0, 1, 2], [2, 1, 3]], 1, 2]], [False, 'boundary']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']]], [['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['open tetra cap', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1]], 1, 2]], [False, 'link']], ['two triangles shared edge', [[[[0, 1, 2], [2, 1, 3]], 1, 2]], [False, 'boundary']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['torus edge', [[[[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]], 0, 1]], [False, 'link']], ['cylinder rung', [[[[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, 5]], [False, 'boundary']], ['strip over-constrained', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 4]], 1, 3]], [False, 'link']]], [['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['cylinder rung', [[[[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, 5]], [False, 'boundary']], ['strip over-constrained', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 4]], 1, 3]], [False, 'link']], ['grid diagonal', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 0, 4]], [True, 'ok']], ['grid interior spoke', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 4, 5]], [True, 'ok']], ['rotated face order', [[[[2, 0, 1], [3, 0, 2]], 0, 2]], [False, 'boundary']]], [['disk spoke', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0, 1]], [True, 'ok']], ['disk rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 1, 2]], [True, 'ok']], ['fan interior edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0, 2]], [False, 'boundary']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['grid interior spoke', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 4, 5]], [True, 'ok']], ['rotated face order', [[[[2, 0, 1], [3, 0, 2]], 0, 2]], [False, 'boundary']]]]
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 spoke[True, 'ok'][True, 'ok']Passed
disk rim[True, 'ok'][True, 'ok']Passed
fan interior edge[False, 'boundary'][False, 'boundary']Passed
fan missing edge[False, 'no-edge'][False, 'no-edge']Passed
tetrahedron edge[True, 'ok'][False, 'minimal']Failed
octahedron edge[True, 'ok'][True, 'ok']Passed
two triangles rim edge[True, 'ok'][True, 'ok']Passed

SHA-256 / 7eeeb864b1d2a25c8e8a4ff911b064049a17199e5aca5db230ebef1826ca7ec0

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,a,b=x
    adj={}
    opp=[]
    ecount={}
    for f in faces:
        for i in range(3):
            u,v=f[i],f[(i+1)%3]
            adj.setdefault(u,set()).add(v)
            adj.setdefault(v,set()).add(u)
            k=(min(u,v),max(u,v))
            ecount[k]=ecount.get(k,0)+1
        if a in f and b in f:
            opp.append([w for w in f if w!=a and w!=b][0])
    k=(min(a,b),max(a,b))
    if k not in ecount: return [False,'no-edge']
    if (adj[a]&adj[b])!=set(opp): return [False,'link']
    bnd=set()
    for (u,v),c in ecount.items():
        if c==1: bnd.update((u,v))
    if a in bnd and b in bnd and ecount[k]!=1: return [False,'boundary']
    if len(adj)<=4: return [False,'minimal']
    return [True,'ok']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk spoke', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0, 1]], [True, 'ok']], ['disk rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 1, 2]], [True, 'ok']], ['fan interior edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0, 2]], [False, 'boundary']], ['fan missing edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 1, 3]], [False, 'no-edge']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['octahedron edge', [[[[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]], [True, 'ok']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']]], [['fan missing edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 1, 3]], [False, 'no-edge']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['octahedron edge', [[[[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]], [True, 'ok']], ['octahedron reversed', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 5, 2]], [True, 'ok']], ['open tetra cap', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1]], 1, 2]], [False, 'link']], ['two triangles shared edge', [[[[0, 1, 2], [2, 1, 3]], 1, 2]], [False, 'boundary']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']]], [['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['open tetra cap', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1]], 1, 2]], [False, 'link']], ['two triangles shared edge', [[[[0, 1, 2], [2, 1, 3]], 1, 2]], [False, 'boundary']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['torus edge', [[[[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]], 0, 1]], [False, 'link']], ['cylinder rung', [[[[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, 5]], [False, 'boundary']], ['strip over-constrained', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 4]], 1, 3]], [False, 'link']]], [['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['cylinder rung', [[[[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, 5]], [False, 'boundary']], ['strip over-constrained', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 4]], 1, 3]], [False, 'link']], ['grid diagonal', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 0, 4]], [True, 'ok']], ['grid interior spoke', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 4, 5]], [True, 'ok']], ['rotated face order', [[[[2, 0, 1], [3, 0, 2]], 0, 2]], [False, 'boundary']]], [['disk spoke', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0, 1]], [True, 'ok']], ['disk rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 1, 2]], [True, 'ok']], ['fan interior edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0, 2]], [False, 'boundary']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['grid interior spoke', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 4, 5]], [True, 'ok']], ['rotated face order', [[[[2, 0, 1], [3, 0, 2]], 0, 2]], [False, 'boundary']]]]
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 spoke[True, 'ok'][True, 'ok']Passed
disk rim[True, 'ok'][True, 'ok']Passed
fan interior edge[False, 'boundary'][False, 'boundary']Passed
fan missing edge[False, 'no-edge'][False, 'no-edge']Passed
tetrahedron edge[False, 'minimal'][False, 'minimal']Passed
octahedron edge[True, 'ok'][True, 'ok']Passed
two triangles rim edge[False, 'minimal'][True, 'ok']Failed

SHA-256 / 75bdc3255117debd62a176d9c206bdc7bcd615badea39d86e3b1893ee0de373f

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,a,b=x
    adj={}
    opp=[]
    ecount={}
    for f in faces:
        for i in range(3):
            u,v=f[i],f[(i+1)%3]
            adj.setdefault(u,set()).add(v)
            adj.setdefault(v,set()).add(u)
            k=(min(u,v),max(u,v))
            ecount[k]=ecount.get(k,0)+1
        if a in f and b in f:
            opp.append([w for w in f if w!=a and w!=b][0])
    k=(min(a,b),max(a,b))
    if k not in ecount: return [False,'no-edge']
    if (adj[a]&adj[b])!=set(opp): return [False,'link']
    bnd=set()
    for (u,v),c in ecount.items():
        if c==1: bnd.update((u,v))
    if a in bnd and b in bnd and ecount[k]!=1: return [False,'boundary']
    if not bnd and len(adj)<=4: return [False,'minimal']
    return [True,'ok']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['disk spoke', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0, 1]], [True, 'ok']], ['disk rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 1, 2]], [True, 'ok']], ['fan interior edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0, 2]], [False, 'boundary']], ['fan missing edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 1, 3]], [False, 'no-edge']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['octahedron edge', [[[[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]], [True, 'ok']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']]], [['fan missing edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 1, 3]], [False, 'no-edge']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['octahedron edge', [[[[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]], [True, 'ok']], ['octahedron reversed', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4]], 5, 2]], [True, 'ok']], ['open tetra cap', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1]], 1, 2]], [False, 'link']], ['two triangles shared edge', [[[[0, 1, 2], [2, 1, 3]], 1, 2]], [False, 'boundary']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']]], [['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['open tetra cap', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1]], 1, 2]], [False, 'link']], ['two triangles shared edge', [[[[0, 1, 2], [2, 1, 3]], 1, 2]], [False, 'boundary']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['torus edge', [[[[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]], 0, 1]], [False, 'link']], ['cylinder rung', [[[[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, 5]], [False, 'boundary']], ['strip over-constrained', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 4]], 1, 3]], [False, 'link']]], [['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['cylinder rung', [[[[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, 5]], [False, 'boundary']], ['strip over-constrained', [[[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 4]], 1, 3]], [False, 'link']], ['grid diagonal', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 0, 4]], [True, 'ok']], ['grid interior spoke', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 4, 5]], [True, 'ok']], ['rotated face order', [[[[2, 0, 1], [3, 0, 2]], 0, 2]], [False, 'boundary']]], [['disk spoke', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 0, 1]], [True, 'ok']], ['disk rim', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 1]], 1, 2]], [True, 'ok']], ['fan interior edge', [[[[0, 1, 2], [0, 2, 3], [0, 3, 4]], 0, 2]], [False, 'boundary']], ['tetrahedron edge', [[[[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]], 0, 1]], [False, 'minimal']], ['two triangles rim edge', [[[[0, 1, 2], [2, 1, 3]], 0, 1]], [True, 'ok']], ['grid interior spoke', [[[[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4], [3, 4, 7], [3, 7, 6], [4, 5, 8], [4, 8, 7]], 4, 5]], [True, 'ok']], ['rotated face order', [[[[2, 0, 1], [3, 0, 2]], 0, 2]], [False, 'boundary']]]]
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 spoke[True, 'ok'][True, 'ok']Passed
disk rim[True, 'ok'][True, 'ok']Passed
fan interior edge[False, 'boundary'][False, 'boundary']Passed
fan missing edge[False, 'no-edge'][False, 'no-edge']Passed
tetrahedron edge[False, 'minimal'][False, 'minimal']Passed
octahedron edge[True, 'ok'][True, 'ok']Passed
two triangles rim edge[True, 'ok'][True, 'ok']Passed

SHA-256 / 335f06d621537f9d1315ea6995109e8fa1afb795234086ef6a1d403ea12fa4ca

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

Case digest / dfe0be94d281fc04ac418d4e7a44d628045a306eccb7ecaebd4ab43cac052539