work / instrument 04 · rev 1
validated
2026python · c# · rhino 8max 2.85e-3validated

Repository · Writing: the reverse problem is two problems · Contact

The reverse problem is two problems. Boundary 1, parameterization recovery, governs positional drift and is the one passthrough closes. Boundary 2, representation richness, governs curvature fidelity and stays open.
fig 01 · the reverse problem, split into two boundaries
01 · problem

Problem

Forward is solved. Take a CAD surface, an analytic description with control points and knots, run the kernel, and tessellate it into a mesh. The reverse is not. Hand that mesh to a solver that pushes on it, and turning the morphed mesh back into an analytic surface that behaves like the part is the open direction. It tends to get treated as a single difficulty: reconstruction is hard, get better at reconstruction.

The handoff a downstream CAD team actually wants is narrow. A surface changed only where the simulation changed it, unchanged everywhere else. That needs a reconstruction that is determined rather than guessed, checkable for validity, and measurable against where it should have landed.

02 · the carry

What crosses the boundary

passthrough carries three layers on the same points. Position, which the solver may change. Identity, a stable label per vertex, which it may not. Topology, the neighbor and winding relationships, which it may not. Vertex 47 is vertex 47 wherever it moves, so correspondence is never inferred.

One contract holds it together: a solver may move positions and is forbidden to renumber or rewire. That single rule is what makes reconstruction, validity, and measurement possible at all.

Carried topology defines which closeness is legal. Two neighbors close together is expected; two non-neighbors landing coincident is flagged as a self-intersection.
fig 02 · carried topology decides which closeness is legal

The validity gate reads the returned geometry against the carried identity and produces three distinct signals.

validity gate · signals
identity labels stopped matching, a remesh
collision labels match but non-neighbors coincide, a self-intersection
fold a face orientation inverted

Collision detection falls out of the carried adjacency directly. Neighbors near each other is expected, non-neighbors coincident is a defect. The topology is the reference that tells a feature from a defect.

03 · calibration

The degraded-information ladder

The result comes from one experiment. Hold the same surface and the same loop, and remove what you carry across the boundary, one rung at a time. The point was to measure where reconstruction stays tractable and where it falls apart.

phase 6 · drift vs carry
rung 1 uv + identity + topology, drift max 1.9e-3, reconstructed
rung 2 identity + topology, uv estimated, drift max 1.9e-2, ~10x worse
rung 3 unordered point cloud, drift 2.58, ill-posed
rung 4 remesh, connectivity changed, blocked by the gate

Positional drift climbs three orders of magnitude as the carry is stripped away. That climb is what carrying identity is worth. The ground is synthetic on purpose: a single surface with known ground truth, built to make the boundary measurable rather than asserted.

04 · action

Reconstruct, then check

A tagged mesh goes out, an external solver morphs it, and it comes back still carrying its identity. Because correspondence is carried rather than inferred, reconstruction is a determined fit. A clean pass writes the reconstructed analytic surface (result.json, schema passthrough.surface.v1), the per-vertex deviation field (field.json), and a status (status.json). The geometry math stays in tested Python. A C# Grasshopper plugin for Rhino 8 is a construction-and-display client that reads the same file contract and rebuilds the surface natively on the canvas.

05 · verification

The two-boundary result

Positional drift behaved exactly as the single-problem story predicted, the clean staircase above. One number refused to follow it. Curvature deviation, how faithfully the reconstruction holds the surface’s curvature, was already saturated at the top of the ladder with full information carried, about 2.2 at the leading edge, and better parameterization did nothing for it. A coarse basis cannot hold a sharp curvature no matter how perfectly the points are placed on it. That number is reported, not tuned away.

The full round trip was validated on Windows in Rhino 8 through the Grasshopper plugin, and the native rebuild matched the Python side exactly.

round-trip · rhino 8matches python
drift max 2.85e-3
curvature max 2.152445
tests 123 on synthetic ground truth

So the reverse problem is two problems wearing one coat.

boundary 1 · parameterization recoveryclosed
Can you recover where the points belong? This governs positional drift, and carrying identity makes it well-posed. It is the boundary passthrough closes.
boundary 2 · representation richnessopen
Can your representation hold the properties the solver feels, like curvature? This is a function of the basis and the degrees of freedom, not of anything you carry. A different knob, and the instrument does not touch it.

The honest version of the work is not “reconstruction works.” It is: here are the two boundaries, here is the one this closes, and here is the one that stays open. The fuller argument, and why the same move shows up far outside geometry, is in the companion essay, the reverse problem is two problems.

06 · artifacts

Artifacts

Repository · Companion essay · Contact

Run it

Requires Python 3.13 and uv. Rhino is optional, only for the Grasshopper side.

git clone https://github.com/barnes-ngb/passthrough
cd passthrough && uv sync
uv run python -m pytest          # 123 passing

# emit the synthetic wing, then run one round trip
uv run python scripts/run_roundtrip.py emit exchange/payload.json --kind good
uv run python scripts/run_roundtrip.py run exchange/payload.json exchange/return --morph clean

Swap --morph clean for collision or fold to watch the validity gate catch an unphysical return instead of a clean one. The degraded-information ladder and its drift-versus-rung plot come from uv run python scripts/run_phase6.py.

WHERE PASSTHROUGH SITS passthrough SurveyLink Directive Engine solver model field execution SMALL LARGE this instrument lit · its neighbors share a system, ordered small to large
fig · where this instrument sits