diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,42 @@
 The serialization format carries its own version tag, independent of the package
 version; any change to it is recorded here explicitly.
 
+## 1.2.0.0
+
+* Add exact rational planar regions and labelled common refinement with one
+  provenance-bearing overlay carrier, closed 0-/1-/2-cell Boolean selection,
+  and grouped polygon publication through the existing DCEL boundary owner.
+* Add exact Euler characteristic and rational area plus symbolic radical length
+  expressions with certified outward-rounded binary64 bounds.
+* Add linear convex-polygon Minkowski convolution, general polygonal addition,
+  and regularized polygonal erosion, opening, closing, offset, and inset through
+  the existing CDT and overlay owners.
+* Curate the exact region algebra through the main Haskell facade. The C ABI and
+  language bindings remain intentionally unchanged.
+* Change the binary wire format to version 6 so round trips preserve the vertex
+  payload plane's optional fill, including the allocation-free unit payload
+  used by geometry-only bulk construction. Version 5 is intentionally
+  unsupported rather than decoded into a different resident representation.
+* Map the dense-storage circle-sweep obstruction exhaustively at the C boundary
+  as obstruction code 55.
+
+## 1.1.0.0
+
+* Add labelled bounded-face components and authoritative component boundaries
+  with counter-clockwise outer loops, clockwise holes, exact collinear-vertex
+  simplification, and typed pinch obstructions.
+* Add exact Delaunay 2-simplex alpha filtration through
+  `alphaShapeContainsFace` and the shared admitted `RadiusSquared` type.
+* Replace `joinSeparatedConstrainedWith` with
+  `joinSeparatedConstrained`; strict separation cannot combine coincident
+  annotations, so the dead combiner and old name are gone.
+* Change the binary wire format to version 5. Structural section counts now
+  occupy one prefix and `decodeTriangulation` requires an explicit
+  `DecodingBudget` plus `TrustedPayloadDecoders`, validating counts,
+  relationships, packed-index bounds, a fixed-body lower bound, and total
+  section elements before allocation while making external decoder trust
+  explicit. Version 4 is intentionally unsupported.
+
 ## 1.0.1.0
 
 * Add the geometry-only `delaunayGeometry` entrance and re-export
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -10,10 +10,11 @@
 fold. Operations return typed obstructions where the finite arena cannot
 represent a result.
 
-Delaunay triangulation, constrained Delaunay (CDT), the Voronoi dual,
-natural-neighbour interpolation, Ruppert refinement, walk point location,
-convex hull, exact Shewchuk predicates, incremental insertion and removal,
-versioned binary serialization.
+Delaunay triangulation, constrained Delaunay (CDT), exact rational planar
+regions and labelled overlay, intrinsic valuations, polygonal Minkowski
+morphology, the Voronoi dual, natural-neighbour interpolation, Ruppert
+refinement, walk point location, convex hull, exact Shewchuk predicates,
+incremental insertion and removal, and versioned binary serialization.
 
 ## Operations
 
@@ -29,6 +30,12 @@
 | `canonicalize` | physical normal form | Construction-independent dense numbering, derived explicitly when required. |
 | `refine` | quality | Steiner insertion, composed *after* any of the above — never a second kind of mesh. |
 | `constrainedDelaunay` | boundary | Sites plus segments, after which interiority is computable. |
+| `overlayLayers` | exact common refinement | One provenance-bearing arrangement whose bounded cells carry both source labels. |
+| `overlayClosedUnion` / `overlayClosedIntersection` / `overlayRegularizedDifference` | planar Boolean selection | A closed exact cell set retaining point- and edge-only results; difference is regularized from selected faces. |
+| `overlaySelectedRegion` | polygon publication | Selected bounded faces with internal arrangement edges dissolved through the existing boundary walker. |
+| `cellValuations` / `regionValuations` | intrinsic measurement | Exact Euler characteristic and rational area plus symbolic radical length with certified bounds. |
+| `minkowskiSum` | polygonal convolution | Exact convex or general polygonal Minkowski addition, with work recorded in a receipt. |
+| `erodeBy` / `openWith` / `closeWith` | regularized morphology | Full-dimensional polygonal erosion and its opening/closing compositions for an origin-anchored convex kernel. |
 
 ### Choose the operation
 
@@ -45,13 +52,22 @@
 | Keep coordinates present in exactly one mesh | `symmetricDifference` | Do not spell it as two differences plus `union`; the direct operation owns the persistent toggle schedule. |
 | Build the first constrained mesh | `constrainedDelaunay` | Do not build unconstrained and treat a rendered outline as topology. |
 | Canonically combine arbitrary constrained meshes | `unionConstrainedWith` or geometry-only `unionConstrained` | Do not use unconstrained `union`; constrained union owns complete conflict witnesses and constraint recovery. |
-| Join two strictly x-separated constrained meshes | `joinSeparatedConstrainedWith` | Use the general constrained union when separation is not proved. |
+| Join two strictly x-separated constrained meshes | `joinSeparatedConstrained` | Separation means annotations are copied, never combined; use the general constrained union when separation is not proved. |
 | Append one constrained section to an authoritative constrained base | `extendConstrainedWith` | Do not use symmetric constrained union when base identity and already-solved constraints must remain resident. |
 | Insert one site persistently | `BulkLoad.insert` or `BulkLoad.insertAt` | Do not open a manual transaction; the singleton entry owns the dense/copy-on-write crossover. |
 | Insert a vector of sites | `BulkLoad.insertMany` | Do not fold singleton insertion; one batch thaws and publishes once. |
 | Compose insertions and removals | one `Session.withSession` | Do not publish every intermediate mesh. Use coordinate-keyed removal after the first compaction. |
 | Refine the whole mesh | `refine` | Do not manufacture a domain witness merely to reach the local API. |
 | Refine a proved face section without changing protected faces | `refineWithinDomain` | Supply its exact permitted faces and interface edges; a guessed boundary is a typed refusal, not a hint. |
+| Partition bounded faces by any caller-owned label | `faceComponents mesh labelFace` | Do not rebuild adjacency outside the DCEL or collapse disconnected regions sharing a label. |
+| Recover one component's polygonal boundary | `componentBoundary mesh component` | Do not emit one polygon per triangle; outer and hole loops have canonical winding and exact collinear simplification. |
+| Combine two labelled polygon layers | `overlayLayers left right` | Do not run separate clipping engines for union, intersection, and difference; select cells from the one common refinement. |
+| Preserve a point- or edge-only Boolean result | `overlayClosedUnion`, `overlayClosedIntersection`, or `overlayRegularizedDifference` | Do not publish immediately as `PlanarRegion`; that carrier is deliberately full-dimensional. |
+| Publish a selected polygonal region | `overlaySelectedRegion predicate overlay` | Do not collect arrangement cells independently; selection must precede boundary descent so internal seams disappear. |
+| Measure a closed exact selection or region | `cellValuations` or `regionValuations` | Do not coerce irrational perimeter to an allegedly exact `Double`; inspect its radical expression and certified interval. |
+| Expand a polygon by another polygon | `minkowskiSum` or `polygonOffset` | Do not add resident mesh vertices pairwise and rebuild; morphology acts on the represented continuum. |
+| Erode, open, or close a polygonal region | `erodeBy`, `openWith`, or `closeWith` | Do not infer a hidden universe or invent lower-dimensional polygons; the result is regularized to representable 2D area. |
+| Keep Delaunay faces below an alpha threshold | `alphaShapeContainsFace threshold mesh` | This is the filled-face filtration, not palette logic and not a claim to expose every simplex of a full alpha-complex carrier. |
 | Require construction-independent numbering | `canonicalize` at the observation boundary | Do not canonicalize every intermediate value; it is intentionally global work. |
 
 ## Foreign bindings
@@ -87,15 +103,33 @@
 | `extendConstrainedWith` | Copy-on-write only for a base of at least 200,000 sites, at most 128 incoming sites, exactly one incoming segment, and a pre-thaw corridor with no resident intersection. Every unmeasured or resident-corridor case stays dense. |
 | `refineWithinDomain` | Dense publication. The local transaction candidate preserved semantics but did not improve wall time, so it was removed. |
 
-On the retained one-million-site / five-thousand-site witnesses, raw
-`difference` takes 0.169 s. Near-full `intersection` takes 0.116 s and 0.100 s
-across the cold and pre-forced contexts. A five-thousand-site
-`symmetricDifference` result takes 0.121 s and allocates 244 MB. Exact
-`siteRelation` takes 0.083 s and 0.115 s across those contexts. Singleton
-insertion takes 5.326 ms. These are raw publication measurements;
-`canonicalize` is measured separately when construction-independent numbering
-is required.
+### Benchmark highlights
 
+Fresh source build on 2026-08-12. The comparison binaries passed exact
+canonical-output agreement before seven interleaved fresh-process timing
+rounds; brackets are the observed range. Lower is better.
+
+| Operation | Workload | Moonlight | Spade 2.15.1 | Result |
+| --- | ---: | ---: | ---: | ---: |
+| Removal | remove 125k / 500k sites | 522.067 ms [514.481–539.392] | 16.576 s [15.304–17.308] | **31.75× faster** |
+| Removal | remove 25k / 100k sites | 72.164 ms [67.604–78.116] | 360.899 ms [264.639–406.783] | **5.00× faster** median |
+| Natural-neighbour interpolation | 2k queries / 400k sites | 192.792 ms [178.064–201.805] | 233.688 ms [214.695–250.986] | **1.21× faster** |
+| Incremental insertion | 500k sites | 33.403 s [32.592–36.813] | 30.506 s [28.809–31.688] | within **1.09×** |
+
+Spade exposes no public triangulation set algebra corresponding to these
+operations. Their independently witnessed Moonlight publication costs are:
+
+| Operation | Workload | Moonlight |
+| --- | ---: | ---: |
+| `siteRelation` | 1m sites vs 995k retained | **96.759 ms** |
+| `intersection` | retain 995k / 1m sites | **109.513 ms** |
+| `symmetricDifference` | publish the 5k-site exclusive result | **119.298 ms** |
+| `difference` | remove 5k / 1m sites | **129.820 ms** |
+| `union` | add 5k / 5m sites | **130.295 ms** |
+
+These are raw publication measurements. `canonicalize` remains a separate,
+explicit observation when construction-independent numbering is required.
+
 ```haskell
 union :: JoinSemilattice annotation
       => Triangulation 'Unconstrained annotation () () ()
@@ -149,13 +183,27 @@
 * Mutation lives in `ST` behind `Moonlight.Triangulation.Internal.Mutable` and
   never escapes.
 * `serialize` is absent from the facade by design; import
-  `Moonlight.Triangulation.Serialization` to reach it.
-* Coordinates are binary64 throughout the public surface. `vertexPoints` and
-  `innerFaceVertexTriples` project dense vectors directly from the authoritative
-  DCEL; callers do not reconstruct a sibling mesh DTO.
+  `Moonlight.Triangulation.Serialization` to reach it. Its canonical decoder
+  requires both a `DecodingBudget` and explicit `TrustedPayloadDecoders`; wire
+  format 6 validates one structural count prefix before decoding defaults,
+  payloads, or allocating a section, and preserves the vertex payload plane's
+  optional fill. The budget covers the input envelope and every module-owned
+  container. `trustedBinaryPayloadDecoders` records the separate caller
+  decision that the selected executable `Binary` decoders have an acceptable
+  internal resource policy. Version 5 is intentionally unsupported.
+* Resident triangulation coordinates are binary64. Exact planar-region APIs use
+  normalized rational `ExactPoint`s; an admitted `OverlayResult` seals those
+  authoritative coordinates beside one certified binary64 DCEL projection.
+  `vertexPoints` and `innerFaceVertexTriples` still project the resident DCEL
+  directly; callers do not reconstruct a sibling mesh DTO.
 * One half-edge mesh underneath. `Cdt` is a mode index on it, not a second
   structure.
 
+The Haskell facade carries the exact region, overlay, valuation, and morphology
+algebra. `ExactCellSet` preserves closed point, edge, and face selections;
+`PlanarRegion` is its full-dimensional polygonal publication. The C ABI and
+Python, TypeScript, and Rust bindings carry the immutable binary64 mesh surface.
+
 ## Use
 
 A triangulation is a value of its site set: construction returns `Either` with
@@ -264,20 +312,24 @@
 
 ### Deriving the boundary
 
-When no boundary is known, the mesh itself carries one. Every Delaunay face
-has a circumradius: faces inside a sampled region sit near the local pitch,
-while faces spanning concavities and voids circumscribe them and blow up.
-Keeping the faces below a threshold derived from the data — a multiple of the
-median circumradius, so no authored constant — is the alpha-complex, and its
-boundary falls out as the edges with exactly one kept side. Those edges are
-already edges of the triangulation, so feeding them to `constrainedDelaunay`
-as index pairs recovers without conflicts, and the parity machinery above
-takes over from there.
+When no boundary is known, classify the Delaunay faces and let the DCEL descend
+them. `faceComponents` evaluates the caller's label once per bounded face and
+keeps disconnected regions distinct even when their labels compare equal.
+`componentBoundary` then returns one counter-clockwise outer loop and clockwise
+hole loops, dropping a boundary vertex only when the library's exact
+orientation predicate proves it collinear and the point lies on the closed
+neighbour segment. A point pinch is a typed `BoundaryPinch`, never an invented
+polygon.
 
+For alpha-shape work the supplied label is simply
+`alphaShapeContainsFace threshold mesh`. `mkRadiusSquared` admits the
+finite non-negative threshold and exact dyadic comparison makes membership
+closed at equality. This is the face, or 2-simplex, filtration; it does not manufacture a second mesh or
+pretend to own palette, colour-space, or rendering policy.
+
 ```haskell
 module Main where
 
-import Data.List (sort, span)
 import GHC.Exts (fromList)
 import Moonlight.Triangulation
 
@@ -296,41 +348,118 @@
       (fail . show)
       pure
       (delaunayGeometry (fromList [Point x y | (x, y) <- pts]))
-  let corners f =
-        [(x, y) | v <- faceVertices mesh f, let Point x y = vertexPoint mesh v]
-      circumradius (ax, ay) (bx, by) (cx, cy) =
-        let dab = sqrt ((bx - ax) ** 2 + (by - ay) ** 2)
-            dbc = sqrt ((cx - bx) ** 2 + (cy - by) ** 2)
-            dca = sqrt ((ax - cx) ** 2 + (ay - cy) ** 2)
-            area2 = abs ((bx - ax) * (cy - ay) - (cx - ax) * (by - ay))
-        in if area2 == 0 then 1 / 0 else dab * dbc * dca / (2 * area2)
-      radii =
-        [ (k, circumradius p q s)
-        | k <- [0 .. numFaces mesh - 1]
-        , [p, q, s] <- [corners (FaceId (fromIntegral k))]
-        ]
-      sorted = sort (map snd radii)
-      medianR = case drop (length sorted `div` 2) sorted of
-        m : _ -> m
-        [] -> 1
-      kept = [FaceId (fromIntegral k) | (k, r) <- radii, r <= 1.35 * medianR]
-      norm (p, q) = if p <= q then (p, q) else (q, p)
-      edges f = case corners f of
-        [p, q, s] -> [norm (p, q), norm (q, s), norm (s, p)]
-        _ -> []
-      runs xs = case xs of
-        [] -> []
-        x : rest -> let (same, more) = span (== x) rest in (x, 1 + length same) : runs more
-      boundary = [e | (e, n) <- runs (sort (concatMap edges kept)), n == 1]
-  print (length radii, length kept, length boundary)
+  threshold <- either (fail . show) pure (mkRadiusSquared 0.4)
+  let components = faceComponents mesh (alphaShapeContainsFace threshold mesh)
+      kept = [(label, component) | (label, component) <- components, label]
+  boundaries <-
+    traverse
+      (either (fail . show) pure . componentBoundary mesh . snd)
+      kept
+  print
+    [ ( length (boundaryLoopVertices (regionBoundaryOuterLoop boundary))
+      , length (regionBoundaryHoleLoops boundary)
+      )
+    | boundary <- boundaries
+    ]
 ```
 
-Output: `(110,96,48)` — 110 finite faces, 96 in the alpha-complex, and the
-derived boundary is exactly the 48 ring edges: both loops recovered from the
-point set alone. The derived boundary walks through data sites, so it is as
-jagged as the sampling; an authored boundary stays smooth at any pitch and
-wins where the generator is known.
+The numeric threshold remains application policy; the circumradius calculation,
+face adjacency, loop extraction, winding, holes, and exact simplification do
+not. The derived boundary walks through data sites, so it is as jagged as the
+sampling; an authored boundary stays smooth at any pitch and wins where the
+generator is known.
 
+### Exact overlay, valuations, and morphology
+
+Author polygonal regions with rational coordinates, refine labelled layers
+once, and select every Boolean from that common arrangement. Closed selection
+retains zero- and one-dimensional intersections; polygon publication dissolves
+all arrangement edges internal to the selected faces.
+
+Valuations read either carrier. Euler characteristic and area remain exact,
+while Euclidean length is a normalized radical expression with certified
+binary64 bounds. Minkowski addition, offset, erosion, opening, and closing then
+compose over the same admitted `PlanarRegion`.
+
+```haskell
+module Main where
+
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.Map.Strict as Map
+import Moonlight.Triangulation
+
+admit :: Show obstruction => Either obstruction value -> IO value
+admit = either (fail . show) pure
+
+rectangleLoop :: Integer -> Integer -> Integer -> Integer -> IO ExactLoop
+rectangleLoop minimumX minimumY maximumX maximumY =
+  admit
+    ( exactLoop
+        ( exactPoint (fromInteger minimumX) (fromInteger minimumY)
+            :| [ exactPoint (fromInteger maximumX) (fromInteger minimumY)
+               , exactPoint (fromInteger maximumX) (fromInteger maximumY)
+               , exactPoint (fromInteger minimumX) (fromInteger maximumY)
+               ]
+        )
+    )
+
+rectangleRegion :: Integer -> Integer -> Integer -> Integer -> IO PlanarRegion
+rectangleRegion minimumX minimumY maximumX maximumY = do
+  outer <- rectangleLoop minimumX minimumY maximumX maximumY
+  component <- admit (polygonComponent outer [])
+  admit (planarRegion [component])
+
+insideLayer :: PlanarRegion -> IO (PlanarLayer Bool)
+insideLayer region = admit (planarLayer False (Map.singleton True region))
+
+cellSummary :: ExactCellSet -> IO (Int, Integer, Integer, CertifiedInterval)
+cellSummary cells = do
+  values <- admit (cellValuations cells)
+  perimeter <- admit (cellSetPerimeter cells)
+  let area = exactAreaValue (valuationArea values)
+  pure
+    ( eulerCharacteristicValue (valuationEuler values)
+    , exactRationalNumerator area
+    , exactRationalDenominator area
+    , exactLengthBounds perimeter
+    )
+
+regionArea :: PlanarRegion -> IO (Integer, Integer)
+regionArea region = do
+  values <- admit (regionValuations region)
+  let area = exactAreaValue (valuationArea values)
+  pure (exactRationalNumerator area, exactRationalDenominator area)
+
+main :: IO ()
+main = do
+  left <- rectangleRegion 0 0 4 4
+  right <- rectangleRegion 2 1 6 3
+  leftLayer <- insideLayer left
+  rightLayer <- insideLayer right
+  overlay <- admit (overlayLayers leftLayer rightLayer)
+
+  unionCells <- admit (overlayClosedUnion id id overlay)
+  intersectionCells <- admit (overlayClosedIntersection id id overlay)
+  differenceCells <- admit (overlayRegularizedDifference id id overlay)
+  traverse cellSummary [unionCells, intersectionCells, differenceCells]
+    >>= print
+
+  kernelLoop <- rectangleLoop (-1) (-1) 1 1
+  kernel <- admit (convexPolygon (exactLoopPoints kernelLoop)) >>= admit . structuringElement
+  (sumRegion, _) <- admit (minkowskiSum left right)
+  (offsetRegion, _) <- admit (polygonOffset kernel left)
+  (insetRegion, _) <- admit (polygonInset kernel left)
+  (openedRegion, _) <- admit (openWith kernel left)
+  (closedRegion, _) <- admit (closeWith kernel left)
+  traverse regionArea [sumRegion, offsetRegion, insetRegion, openedRegion, closedRegion]
+    >>= print
+```
+
+The Boolean summaries are union `(χ = 1, area = 20, perimeter = 20)`,
+intersection `(1, 4, 8)`, and regularized difference `(1, 12, 20)`. The
+morphology areas are `(48, 36, 4, 16, 16)`: Minkowski sum, one-unit square
+offset, inset, opening, and closing respectively.
+
 ## The growing city
 
 A triangulation is a value so that a large one can be extended without being
@@ -373,10 +502,10 @@
 ## Predicates
 
 `Moonlight.Triangulation.Internal.Dyadic` carries the exact layer: mantissas are
-decoded and aligned to a common exponent, and determinants are evaluated over
-`Integer`. The floating approximation is trusted only inside Shewchuk's error
-bounds — `(3 + 16u)u` for orientation, `(10 + 96u)u` for incircle — and falls
-through to the exact evaluation otherwise.
+decoded and aligned to a common exponent, with bounded predicates evaluated in
+machine words and larger cases over `Integer`. Floating approximations are
+trusted only inside proved error bounds and fall through to exact evaluation
+otherwise.
 
 ## Public sublibraries
 
@@ -387,8 +516,8 @@
 | Sublibrary | Surface |
 | --- | --- |
 | `core` | Exact-arithmetic scalars (`Scalar`, `LineSideInfo`) over paged, copy-on-write storage (`Internal.Dyadic`, `.Paged`, `.BoxedPaged`, `.PageDirectory`, `.Growable`, `.PackedIndex`, `.FaceQueue`). Depends only on `base`/`containers`/`deepseq`/`vector` — no other sublibrary. |
-| `dcel` | The finite half-edge mesh and its whole read surface: `Types`, `Math`, `Interop`, `Dcel`, `Payload`, `JoinSemilattice`, `Handles` with its iterator family, `PointLocation`, `Validation`, `FloodFillIterator`, `IntersectionIterator`. Adds `primitive` and `vector-algorithms` over `core`. |
-| `build` | Everything that *constructs*: `BulkLoad`, `Session`, `Removal`, typed `SetAlgebra`, `Cdt` constraint recovery, and `Refinement`. Adds no external dependency over `core` and `dcel`. |
+| `dcel` | The finite half-edge mesh and its whole read surface: `Types`, `Math`, exact geometry, closed `CellSet`, `Region`, `Valuation`, `Interop`, `Dcel`, `Payload`, `JoinSemilattice`, `Handles` with its iterator family, `PointLocation`, `Validation`, `FloodFillIterator`, `IntersectionIterator`. Adds `primitive` and `vector-algorithms` over `core`. |
+| `build` | Everything that *constructs*: `BulkLoad`, `Session`, `Removal`, typed `SetAlgebra`, `Cdt` constraint recovery, `Overlay`, `Minkowski`, and `Refinement`. Adds no external dependency over `core` and `dcel`. |
 | `parallel` | Concurrent evaluation of the pure union plan. Adds `async` at this effect boundary rather than below it. |
 | `serialize` | `Serialization` — the versioned binary envelope, and the only sublibrary that costs you `binary`, `bytestring`, and `transformers`. Deliberately absent from the facade. |
 | `dual` | The Voronoi dual and what reads it: `Voronoi`, `Voronoi.Handles`, `Interpolation` (natural-neighbour), `HintGenerator` (Delaunay hierarchy hints). Sits over `core`, `dcel` and `build`. |
@@ -400,7 +529,13 @@
 * `.BulkLoad` — circle-sweep construction and incremental insertion.
 * `.Cdt` — constraint recovery by conflict strip, with requeue on re-intersection.
 * `.PointLocation`, `.HintGenerator` — walk location and Delaunay hierarchy hints.
-* `.IntersectionIterator`, `.FloodFillIterator` — ordered line traversal and barrier fill.
+* `.IntersectionIterator`, `.FloodFillIterator` — ordered line traversal,
+  barrier fill, labelled face components, boundary loops, and alpha-face
+  filtration.
+* `.Exact`, `.CellSet`, `.Region`, `.Valuation` — exact rational geometry,
+  closed cell selections, admitted polygonal regions, and intrinsic measures.
+* `.Overlay`, `.Minkowski` — labelled common refinement, planar Booleans,
+  polygonal convolution, offset, erosion, opening, and closing.
 * `.Voronoi`, `.Interpolation` — dual cells and natural-neighbour interpolation.
 * `.Refinement` — Ruppert-style angle and area refinement.
 * `.Validation` — structural and Delaunay-property audits.
diff --git a/bench/aggregate/Main.hs b/bench/aggregate/Main.hs
--- a/bench/aggregate/Main.hs
+++ b/bench/aggregate/Main.hs
@@ -8,6 +8,7 @@
 import qualified Moonlight.Triangulation.DcelBench as DcelBench
 import qualified Moonlight.Triangulation.DualBench as DualBench
 import qualified Moonlight.Triangulation.JoinBench as JoinBench
+import qualified Moonlight.Triangulation.RegionBench as RegionBench
 
 main :: IO ()
 main = do
@@ -15,3 +16,4 @@
   DcelBench.benchmarks
   DualBench.benchmarks
   JoinBench.benchmarks
+  RegionBench.benchmarks
diff --git a/bench/dcel/Moonlight/Triangulation/DcelBench.hs b/bench/dcel/Moonlight/Triangulation/DcelBench.hs
--- a/bench/dcel/Moonlight/Triangulation/DcelBench.hs
+++ b/bench/dcel/Moonlight/Triangulation/DcelBench.hs
@@ -1,13 +1,18 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE DataKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE NumericUnderscores #-}
 
 -- | Read-side traversal over a finished mesh: ordered line intersection and the
 -- circle shape query. Construction here is fixture cost, not the subject.
 module Moonlight.Triangulation.DcelBench (benchmarks) where
 
-import BenchSupport (randomPoints, requireRight, timedValue)
+import BenchSupport
+  ( latticeFaceBand
+  , latticePoints
+  , randomPoints
+  , requireRight
+  , timedValue
+  )
 import Control.DeepSeq (force)
 import Control.Exception (evaluate)
 import qualified Data.Vector as V
@@ -16,7 +21,9 @@
 import Moonlight.Triangulation.IntersectionIterator (lineIntersections)
 
 benchmarks :: IO ()
-benchmarks = benchmarkQueries 20_000 10_000
+benchmarks = do
+  benchmarkQueries 20_000 10_000
+  benchmarkRegionWorkload 440 272 22
 
 benchmarkQueries :: Int -> Int -> IO ()
 benchmarkQueries pointCount queryCount = do
@@ -41,3 +48,91 @@
     -> Int
   lineCount triangulation queries stride !accumulator index from =
     accumulator + length (lineIntersections triangulation from (queries V.! (index + stride)))
+
+-- | The workload that motivated region extraction: 239,360 bounded faces.
+-- Construction is shared fixture cost and is forced before either timed lane.
+benchmarkRegionWorkload :: Int -> Int -> Int -> IO ()
+benchmarkRegionWorkload widthInCells heightInCells expectedBandCount = do
+  built <-
+    requireRight
+      (delaunay unitElementDefaults (latticePoints widthInCells heightInCells))
+  triangulation <- evaluate (force (buildTriangulation built))
+  benchmarkRegionBoundaries triangulation expectedBandCount
+  benchmarkAlphaFaceMembership triangulation
+
+benchmarkRegionBoundaries :: DelaunayTriangulation Point -> Int -> IO ()
+benchmarkRegionBoundaries triangulation expectedBandCount = do
+  analysed <-
+    timedValue
+      "face-components-and-boundaries"
+      (evaluate (force (regionAnalysis triangulation)))
+  (components, boundaries) <- requireRight analysed
+  let faceCount = sum (fmap (length . faceComponentFaces . snd) components)
+      outerLoopCount = length boundaries
+      holeLoopCount =
+        sum (fmap (length . regionBoundaryHoleLoops) boundaries)
+      boundaryVertexCount =
+        sum
+          ( fmap
+              (length . boundaryLoopVertices . regionBoundaryOuterLoop)
+              boundaries
+          )
+  if
+    ( faceCount
+    , length components
+    , outerLoopCount
+    , holeLoopCount
+    , boundaryVertexCount
+    )
+      == (239_360, expectedBandCount, expectedBandCount, 0, 88)
+    then
+      putStrLn
+        "face-components-and-boundaries-receipt: faces=239360 components=22 outer-loops=22 hole-loops=0 boundary-vertices=88"
+    else
+      fail
+        ( "region benchmark receipt mismatch: "
+            <> show
+              ( faceCount
+              , length components
+              , outerLoopCount
+              , holeLoopCount
+              , boundaryVertexCount
+              )
+        )
+ where
+  regionAnalysis
+    :: DelaunayTriangulation Point
+    -> Either
+        BoundaryObstruction
+        ([(Int, FaceComponent)], [RegionBoundary])
+  regionAnalysis mesh = do
+    let components = faceComponents mesh (latticeFaceBand mesh)
+    boundaries <-
+      traverse
+        (componentBoundary mesh . snd)
+        components
+    pure (components, boundaries)
+
+benchmarkAlphaFaceMembership :: DelaunayTriangulation Point -> IO ()
+benchmarkAlphaFaceMembership triangulation = do
+  threshold <- requireRight (mkRadiusSquared 0.5)
+  let containsFace = alphaShapeContainsFace threshold triangulation
+  admittedCount <-
+    timedValue
+      "alpha-face-membership"
+      ( evaluate
+          ( force
+              ( foldl'
+                  (\count face ->
+                     if containsFace face
+                       then count + 1
+                       else count
+                  )
+                  (0 :: Int)
+                  (innerFaces triangulation)
+              )
+          )
+      )
+  if admittedCount == 239_360
+    then putStrLn "alpha-face-membership-receipt: admitted=239360"
+    else fail ("alpha face membership receipt mismatch: " <> show admittedCount)
diff --git a/bench/region/Main.hs b/bench/region/Main.hs
new file mode 100644
--- /dev/null
+++ b/bench/region/Main.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import qualified Moonlight.Triangulation.RegionBench as RegionBench
+
+main :: IO ()
+main = RegionBench.benchmarks
diff --git a/bench/region/Moonlight/Triangulation/RegionBench.hs b/bench/region/Moonlight/Triangulation/RegionBench.hs
new file mode 100644
--- /dev/null
+++ b/bench/region/Moonlight/Triangulation/RegionBench.hs
@@ -0,0 +1,444 @@
+{-# LANGUAGE NumericUnderscores #-}
+
+-- | Exact segment-event, overlay, and grouped-region publication receipts.
+-- The source families are closed data; measurement is the only effect.
+module Moonlight.Triangulation.RegionBench (benchmarks) where
+
+import BenchSupport
+  ( latticeFaceBand
+  , latticePoints
+  , requireRight
+  , timedValue
+  )
+import Control.DeepSeq (force)
+import Control.Exception (evaluate)
+import Data.Foldable (traverse_)
+import qualified Data.Map.Strict as Map
+import Data.List.NonEmpty (NonEmpty (..))
+import Moonlight.Triangulation
+  ( buildTriangulation
+  , delaunay
+  , unitElementDefaults
+  )
+import Moonlight.Triangulation.CellSet
+  ( exactCellSet
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , exactCellSetVertexCount
+  )
+import Moonlight.Triangulation.Dcel (numInnerFaces)
+import Moonlight.Triangulation.Exact (ExactPoint, exactPoint)
+import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))
+import Moonlight.Triangulation.Overlay
+  ( OverlayReceipt (..)
+  , overlayClosedIntersection
+  , overlayLayers
+  , overlayReceipt
+  )
+import Moonlight.Triangulation.Minkowski
+  ( MinkowskiReceipt
+  , convexMinkowskiSum
+  , convexPolygon
+  , erodeBy
+  , minkowskiExactCoordinateBitGrowth
+  , minkowskiExactCrossings
+  , minkowskiGeneratedConvolutionEdges
+  , minkowskiGeneratedPieces
+  , minkowskiOutputCells
+  , minkowskiOverlayPasses
+  , minkowskiSum
+  , structuringElement
+  )
+import Moonlight.Triangulation.Region
+  ( PlanarLayer
+  , PolygonComponent
+  , RegionValidationError
+  , exactLoop
+  , exactLoopPoints
+  , labelledPlanarLayer
+  , planarLayer
+  , planarLayerRegions
+  , planarRegion
+  , planarRegionComponents
+  , polygonComponent
+  , polygonHoleLoops
+  , polygonOuterLoop
+  )
+import Moonlight.Triangulation.Valuation
+  ( cellValuations
+  , eulerCharacteristicValue
+  , exactLengthTerms
+  , regionValuations
+  , valuationEuler
+  , valuationIntrinsic1
+  , exactLengthExpression
+  )
+
+data OverlayFamily
+  = DisjointFamily
+  | GridCrossingFamily
+  | CollinearOverlapFamily
+  deriving stock (Eq, Ord, Show)
+
+benchmarks :: IO ()
+benchmarks = do
+  traverse_
+    (\family -> traverse_ (benchmarkOverlayFamily family) [2, 4, 8])
+    [DisjointFamily, GridCrossingFamily, CollinearOverlapFamily]
+  benchmarkPublicationReceipt
+  traverse_ benchmarkRegionAuthoring [64, 256, 1_024]
+  traverse_ benchmarkRegionValuation [64, 256, 1_024]
+  traverse_ benchmarkConvexMinkowski [8, 32, 128, 512]
+  traverse_
+    (\family -> traverse_ (benchmarkOverlaySelectorFamily family) [2, 4, 8])
+    [DisjointFamily, GridCrossingFamily, CollinearOverlapFamily]
+  benchmarkGeneralMorphology
+
+benchmarkOverlayFamily :: OverlayFamily -> Int -> IO ()
+benchmarkOverlayFamily family size = do
+  layers <- requireRight (familyLayers family size)
+  result <-
+    timedValue
+      (familyName family <> "-n" <> show size)
+      (requireRight (uncurry overlayLayers layers))
+  let receipt = overlayReceipt result
+      inputAndOutput = overlayInputSegments receipt + overlayRelationEvents receipt
+      logarithmicScale = max 1 (ceilingLog2 (overlayInputSegments receipt + 1))
+      totalLimit = 128 * inputAndOutput * logarithmicScale
+  putStrLn
+    ( familyName family
+        <> "-receipt: n="
+        <> show size
+        <> " source-segments="
+        <> show (overlayInputSegments receipt)
+        <> " relation-events-k="
+        <> show (overlayRelationEvents receipt)
+        <> " exact-relation-checks="
+        <> show (overlayTotalRelationChecks receipt)
+        <> " atomic-edges="
+        <> show (overlayAtomicEdges receipt)
+        <> " arrangement-cells="
+        <> show (overlayArrangementCells receipt)
+        <> " resident-faces="
+        <> show (overlayResidentFaces receipt)
+        <> " avl-height="
+        <> show (overlaySweepMaximumHeight receipt)
+    )
+  if overlayTotalRelationChecks receipt <= totalLimit
+    then pure ()
+    else
+      fail
+        ( familyName family
+            <> " retained superlinear total relation work: "
+            <> show (overlayTotalRelationChecks receipt, totalLimit)
+        )
+
+benchmarkOverlaySelectorFamily :: OverlayFamily -> Int -> IO ()
+benchmarkOverlaySelectorFamily family size = do
+  layers <- requireRight (familyLayers family size)
+  result <- requireRight (uncurry overlayLayers layers)
+  selectedReceipt <-
+    timedValue
+      (familyName family <> "-selector-n" <> show size)
+      (do
+         selected <-
+           requireRight
+             (overlayClosedIntersection (== 1) (== 1) result)
+         valuations <- requireRight (cellValuations selected)
+         pure
+           ( exactCellSetVertexCount selected
+           , exactCellSetEdgeCount selected
+           , exactCellSetFaceCount selected
+           , eulerCharacteristicValue (valuationEuler valuations)
+           , length
+               ( exactLengthTerms
+                   (exactLengthExpression (valuationIntrinsic1 valuations))
+               )
+           ))
+  putStrLn
+    ( familyName family
+        <> "-selector-receipt: n="
+        <> show size
+        <> " cells="
+        <> show selectedReceipt
+    )
+
+familyName :: OverlayFamily -> String
+familyName DisjointFamily = "overlay-disjoint"
+familyName GridCrossingFamily = "overlay-grid-crossing"
+familyName CollinearOverlapFamily = "overlay-collinear-overlap"
+
+benchmarkRegionAuthoring :: Int -> IO ()
+benchmarkRegionAuthoring size = do
+  componentCount <-
+    timedValue
+      ("planar-region-disjoint-authoring-n" <> show size)
+      (requireRight $ do
+         components <-
+           traverse
+             rectangleComponent
+             [ (3 * index, 0, 3 * index + 1, 1)
+             | index <- [0 .. size - 1]
+             ]
+         length . planarRegionComponents <$> planarRegion components)
+  if componentCount == size
+    then
+      putStrLn
+        ( "planar-region-disjoint-authoring-receipt: n="
+            <> show size
+            <> " components="
+            <> show componentCount
+        )
+    else fail ("planar region authoring lost components: " <> show componentCount)
+
+benchmarkRegionValuation :: Int -> IO ()
+benchmarkRegionValuation size = do
+  components <-
+    requireRight
+      ( traverse
+          rectangleComponent
+          [ (3 * index, 0, 3 * index + 1, 1)
+          | index <- [0 .. size - 1]
+          ]
+      )
+  region <- requireRight (planarRegion components)
+  valuations <-
+    timedValue
+      ("planar-region-valuations-n" <> show size)
+      (evaluate . force =<< requireRight (regionValuations region))
+  let receipt =
+        ( eulerCharacteristicValue (valuationEuler valuations)
+        , length
+            ( exactLengthTerms
+                (exactLengthExpression (valuationIntrinsic1 valuations))
+            )
+        )
+  if receipt == (size, 1)
+    then
+      putStrLn
+        ( "planar-region-valuations-receipt: n="
+            <> show size
+            <> " euler="
+            <> show size
+            <> " radical-terms=1"
+        )
+    else fail ("planar region valuation receipt mismatch: " <> show receipt)
+
+benchmarkConvexMinkowski :: Int -> IO ()
+benchmarkConvexMinkowski halfSize = do
+  left <- requireRight (convexPolygon (convexLens halfSize))
+  right <- requireRight (convexPolygon (convexLens halfSize))
+  result <-
+    timedValue
+      ("convex-minkowski-n" <> show (2 * halfSize))
+      (evaluate (force (convexMinkowskiSum left right)))
+  let outputVertices =
+        sum
+          [ length (exactLoopPoints (polygonOuterLoop component))
+          | component <- planarRegionComponents result
+          ]
+      inputVertices = 4 * halfSize
+  if outputVertices <= inputVertices
+    then
+      putStrLn
+        ( "convex-minkowski-receipt: input-vertices="
+            <> show inputVertices
+            <> " output-vertices="
+            <> show outputVertices
+        )
+    else fail ("convex Minkowski output exceeded n+m: " <> show (outputVertices, inputVertices))
+
+benchmarkGeneralMorphology :: IO ()
+benchmarkGeneralMorphology = do
+  concaveLoop <-
+    requireRight
+      ( exactLoop
+          ( integerPoint 0 0
+              :| [ integerPoint 6 0
+                 , integerPoint 6 2
+                 , integerPoint 2 2
+                 , integerPoint 2 6
+                 , integerPoint 0 6
+                 ]
+          )
+      )
+  concaveComponent <- requireRight (polygonComponent concaveLoop [])
+  concaveRegion <- requireRight (planarRegion [concaveComponent])
+  kernelComponent <- requireRight (rectangleComponent (-1, -1, 1, 1))
+  kernelRegion <- requireRight (planarRegion [kernelComponent])
+  kernelPolygon <-
+    requireRight
+      (convexPolygon (exactLoopPoints (polygonOuterLoop kernelComponent)))
+  element <- requireRight (structuringElement kernelPolygon)
+  (sumComponents, sumReceipt) <-
+    timedValue
+      "general-minkowski-concave"
+      (do
+         (result, receipt) <- requireRight (minkowskiSum concaveRegion kernelRegion)
+         pure (length (planarRegionComponents result), receipt))
+  printMorphologyReceipt "general-minkowski-concave" sumComponents sumReceipt
+  erosionSourceComponents <-
+    requireRight
+      ( traverse
+          rectangleComponent
+          [(0, 0, 6, 6), (9, 0, 15, 6)]
+      )
+  erosionSource <- requireRight (planarRegion erosionSourceComponents)
+  (erosionComponents, erosionReceipt) <-
+    timedValue
+      "general-erosion-disconnected"
+      (do
+         (result, receipt) <- requireRight (erodeBy element erosionSource)
+         pure (length (planarRegionComponents result), receipt))
+  printMorphologyReceipt
+    "general-erosion-disconnected"
+    erosionComponents
+    erosionReceipt
+  if minkowskiOverlayPasses sumReceipt > 0
+      && minkowskiGeneratedPieces sumReceipt > 0
+      && minkowskiOverlayPasses erosionReceipt > 0
+    then pure ()
+    else fail "general morphology bypassed its declared decomposition/overlay work"
+
+printMorphologyReceipt :: String -> Int -> MinkowskiReceipt -> IO ()
+printMorphologyReceipt label outputComponents receipt =
+  putStrLn
+    ( label
+        <> "-receipt: output-components="
+        <> show outputComponents
+        <> " generated-pieces="
+        <> show (minkowskiGeneratedPieces receipt)
+        <> " convolution-edges="
+        <> show (minkowskiGeneratedConvolutionEdges receipt)
+        <> " overlay-passes="
+        <> show (minkowskiOverlayPasses receipt)
+        <> " exact-crossings="
+        <> show (minkowskiExactCrossings receipt)
+        <> " output-cells="
+        <> show (minkowskiOutputCells receipt)
+        <> " coordinate-bit-growth="
+        <> show (minkowskiExactCoordinateBitGrowth receipt)
+    )
+
+convexLens :: Int -> NonEmpty ExactPoint
+convexLens halfSize =
+  let maximumIndex = max 1 (halfSize - 1)
+      height = 2 * maximumIndex * maximumIndex + 1
+      lower =
+        [ integerPoint index (index * index)
+        | index <- [1 .. maximumIndex]
+        ]
+      upper =
+        [ integerPoint index (height - index * index)
+        | index <- reverse [0 .. maximumIndex]
+        ]
+   in integerPoint 0 0 :| (lower <> upper)
+
+familyLayers
+  :: OverlayFamily
+  -> Int
+  -> Either RegionValidationError (PlanarLayer Int, PlanarLayer Int)
+familyLayers family size =
+  case family of
+    DisjointFamily ->
+      (,)
+        <$> layerFromRectangles
+          [ (3 * index, 0, 3 * index + 1, 1)
+          | index <- [0 .. size - 1]
+          ]
+        <*> planarLayer 0 Map.empty
+    GridCrossingFamily ->
+      (,)
+        <$> layerFromRectangles
+          [ (3 * index, 0, 3 * index + 1, 3 * size - 1)
+          | index <- [0 .. size - 1]
+          ]
+        <*> layerFromRectangles
+          [ (0, 3 * index, 3 * size - 1, 3 * index + 1)
+          | index <- [0 .. size - 1]
+          ]
+    CollinearOverlapFamily ->
+      (,)
+        <$> layerFromRectangles
+          [ (3 * index, 0, 3 * index + 2, 2)
+          | index <- [0 .. size - 1]
+          ]
+        <*> layerFromRectangles
+          [ (3 * index + 1, 0, 3 * index + 3, 1)
+          | index <- [0 .. size - 1]
+          ]
+
+layerFromRectangles
+  :: [(Int, Int, Int, Int)]
+  -> Either RegionValidationError (PlanarLayer Int)
+layerFromRectangles rectangles = do
+  components <- traverse rectangleComponent rectangles
+  region <- planarRegion components
+  planarLayer 0 (Map.singleton 1 region)
+
+rectangleComponent
+  :: (Int, Int, Int, Int)
+  -> Either RegionValidationError PolygonComponent
+rectangleComponent (minimumX, minimumY, maximumX, maximumY) = do
+  loop <-
+    exactLoop
+      ( integerPoint minimumX minimumY
+          :| [ integerPoint maximumX minimumY
+             , integerPoint maximumX maximumY
+             , integerPoint minimumX maximumY
+             ]
+      )
+  polygonComponent loop []
+
+integerPoint :: Int -> Int -> ExactPoint
+integerPoint x y =
+  exactPoint
+    (fromIntegral x)
+    (fromIntegral y)
+
+benchmarkPublicationReceipt :: IO ()
+benchmarkPublicationReceipt = do
+  built <-
+    requireRight (delaunay unitElementDefaults (latticePoints 440 272))
+  triangulation <- evaluate (force (buildTriangulation built))
+  published <-
+    timedValue
+      "labelled-planar-layer-publication"
+      (requireRight (labelledPlanarLayer (-1) triangulation (latticeFaceBand triangulation)))
+  let components =
+        concatMap planarRegionComponents (Map.elems (planarLayerRegions published))
+      holeCount = sum (map (length . polygonHoleLoops) components)
+      exactCoordinateCount =
+        sum
+          [ length (exactLoopPoints (polygonOuterLoop component))
+              + sum (map (length . exactLoopPoints) (polygonHoleLoops component))
+          | component <- components
+          ]
+      receipt =
+        ( numInnerFaces triangulation
+        , length components
+        , length components
+        , holeCount
+        , exactCoordinateCount
+        )
+  if receipt == (239_360, 22, 22, 0, 88)
+    then
+      putStrLn
+        "labelled-planar-layer-publication-receipt: faces=239360 components=22 outer-loops=22 holes=0 exact-coordinates=88"
+    else fail ("labelled planar layer receipt mismatch: " <> show receipt)
+  sparseReceipt <-
+    timedValue
+      "exact-cell-set-sparse-selection"
+      (do
+         selected <- requireRight (exactCellSet triangulation [VertexId 0] [] [])
+         pure
+           ( exactCellSetVertexCount selected
+           , exactCellSetEdgeCount selected
+           , exactCellSetFaceCount selected
+           ))
+  if sparseReceipt == (1, 0, 0)
+    then putStrLn "exact-cell-set-sparse-selection-receipt: vertices=1 edges=0 faces=0"
+    else fail ("sparse exact cell selection receipt mismatch: " <> show sparseReceipt)
+
+ceilingLog2 :: Int -> Int
+ceilingLog2 target = length (takeWhile (< target) (iterate (* 2) 1))
diff --git a/bench/support/BenchSupport.hs b/bench/support/BenchSupport.hs
--- a/bench/support/BenchSupport.hs
+++ b/bench/support/BenchSupport.hs
@@ -8,14 +8,28 @@
   ( timedValue
   , requireRight
   , randomPoints
+  , latticePoints
+  , latticeFaceBand
   ) where
 
 import Control.DeepSeq (NFData, force)
 import Control.Exception (evaluate)
 import Data.Word (Word64)
+import qualified Data.Vector as V
 import GHC.Clock (getMonotonicTimeNSec)
-import GHC.Stats (RTSStats (allocated_bytes), getRTSStats, getRTSStatsEnabled)
-import Moonlight.Triangulation (Point (Point))
+import GHC.Stats
+  ( RTSStats (allocated_bytes, max_live_bytes)
+  , getRTSStats
+  , getRTSStatsEnabled
+  )
+import Moonlight.Triangulation
+  ( FaceId
+  , Point (Point)
+  , Triangulation
+  , faceVertices
+  , pointX
+  , vertexPoint
+  )
 import System.CPUTime (getCPUTime)
 
 -- | Both clocks, because they answer different questions and neither
@@ -39,7 +53,9 @@
   putStrLn (label <> "-cpu: " <> show (fromIntegral (cpuEnd - cpuStart) / 1.0e12 :: Double) <> "s")
   case (before, after) of
     (Just left, Just right) ->
-      putStrLn (label <> "-allocated-bytes: " <> show (allocated_bytes right - allocated_bytes left))
+      do
+        putStrLn (label <> "-allocated-bytes: " <> show (allocated_bytes right - allocated_bytes left))
+        putStrLn (label <> "-max-live-bytes: " <> show (max_live_bytes right))
     _ -> pure ()
   pure value
 
@@ -58,3 +74,29 @@
         unit :: Word64 -> Double
         unit value = fromIntegral (value `div` 2048) / 9_007_199_254_740_992
      in Point (2 * unit state1 - 1) (2 * unit state2 - 1) : go state2
+
+-- | The canonical large planar-region fixture. Both the DCEL control and the
+-- exact publication arm consume this one source rather than quietly drifting
+-- into merely similar grids.
+latticePoints :: Int -> Int -> V.Vector Point
+latticePoints widthInCells heightInCells =
+  V.generate
+    ((widthInCells + 1) * (heightInCells + 1))
+    ( \index ->
+        let (row, column) = index `quotRem` (widthInCells + 1)
+         in Point (fromIntegral column) (fromIntegral row)
+    )
+
+-- | Twenty-column connected face bands used by the frozen 239,360-face
+-- publication receipt.
+latticeFaceBand
+  :: Triangulation mode Point directed undirected face
+  -> FaceId
+  -> Int
+latticeFaceBand triangulation face =
+  let xSum =
+        foldl'
+          (\accumulator vertex -> accumulator + pointX (vertexPoint triangulation vertex))
+          0
+          (faceVertices triangulation face)
+   in floor (xSum / 3) `quot` 20
diff --git a/moonlight-triangulation.cabal b/moonlight-triangulation.cabal
--- a/moonlight-triangulation.cabal
+++ b/moonlight-triangulation.cabal
@@ -1,9 +1,12 @@
 cabal-version:       3.4
 name:                moonlight-triangulation
-version:             1.0.1.0
-synopsis:            Delaunay triangulations as a lawful finite-set algebra.
+version:             1.2.0.0
+synopsis:            Delaunay meshes and exact planar-region algebra.
 description:         Delaunay and constrained Delaunay triangulation as a lawful
-                     finite-set algebra: a mesh is a value of its site set, so
+                     finite-set algebra, together with exact rational planar
+                     regions, labelled common refinement, intrinsic valuations,
+                     and polygonal Minkowski morphology. A mesh is a value of
+                     its site set, so
                      union, intersection and difference return triangulations
                      and refinement composes after them rather than replacing
                      them. One structure-of-arrays half-edge mesh carries the
@@ -86,6 +89,7 @@
     Moonlight.Triangulation.Scalar
     Moonlight.Triangulation.LineSideInfo
     Moonlight.Triangulation.Internal.Dyadic
+    Moonlight.Triangulation.Internal.ExactRational
     Moonlight.Triangulation.Internal.PageDirectory
     Moonlight.Triangulation.Internal.Paged
     Moonlight.Triangulation.Internal.BoxedPaged
@@ -106,6 +110,10 @@
   exposed-modules:
     Moonlight.Triangulation.Types
     Moonlight.Triangulation.Math
+    Moonlight.Triangulation.Exact
+    Moonlight.Triangulation.CellSet
+    Moonlight.Triangulation.Region
+    Moonlight.Triangulation.Valuation
     Moonlight.Triangulation.Interop
     Moonlight.Triangulation.Dcel
     Moonlight.Triangulation.Payload
@@ -123,6 +131,12 @@
     Moonlight.Triangulation.FloodFillIterator
     Moonlight.Triangulation.IntersectionIterator
     Moonlight.Triangulation.Internal.FaceProbe
+    Moonlight.Triangulation.Internal.CellSet
+    Moonlight.Triangulation.Internal.BoundaryCycle
+    Moonlight.Triangulation.Internal.ExactSegmentEvents
+    Moonlight.Triangulation.Internal.Region.Publication
+    Moonlight.Triangulation.Internal.Region.Types
+    Moonlight.Triangulation.Internal.SegmentRelation
     Moonlight.Triangulation.Internal.Types
     Moonlight.Triangulation.Internal.Representation
     Moonlight.Triangulation.Internal.PointIndex
@@ -140,6 +154,8 @@
     Moonlight.Triangulation.Internal.DcelOperations.Subdivide
     Moonlight.Triangulation.Internal.DcelOperations.Twin
     Moonlight.Triangulation.Internal.Canonical
+  other-modules:
+    Moonlight.Triangulation.Internal.Region.Bounds
   build-depends:
     base >= 4.20 && < 5
     , containers >= 0.8 && < 0.9
@@ -153,18 +169,24 @@
 library build
   import: shared-properties
   visibility: public
-  hs-source-dirs: src-build
+  hs-source-dirs:
+    src-build
+    src-embedding
   exposed-modules:
     Moonlight.Triangulation.BulkLoad
     Moonlight.Triangulation.Removal
     Moonlight.Triangulation.Session
     Moonlight.Triangulation.Cdt
+    Moonlight.Triangulation.Minkowski
+    Moonlight.Triangulation.Overlay
     Moonlight.Triangulation.Refinement
     Moonlight.Triangulation.SetAlgebra
     Moonlight.Triangulation.Internal.Cdt.Build
     Moonlight.Triangulation.Internal.Cdt.Query
     Moonlight.Triangulation.Internal.Cdt.Types
     Moonlight.Triangulation.Internal.Cdt.Union
+    Moonlight.Triangulation.Internal.Overlay.Arrangement
+    Moonlight.Triangulation.Internal.Overlay.Resident
     Moonlight.Triangulation.Internal.Join
     Moonlight.Triangulation.Internal.Join.Seam
   other-modules:
@@ -188,6 +210,10 @@
     Moonlight.Triangulation.Internal.Join.Plan
     Moonlight.Triangulation.Internal.Join.Rebuild
     Moonlight.Triangulation.Internal.Join.SiteSet
+    Moonlight.Triangulation.Internal.Overlay.Embedding
+    Moonlight.Triangulation.Internal.Overlay.Types
+    Moonlight.Triangulation.Internal.Minkowski.Convex
+    Moonlight.Triangulation.Internal.Minkowski.Types
   build-depends:
     base >= 4.20 && < 5
     , containers >= 0.8 && < 0.9
@@ -301,7 +327,7 @@
     moonlight_triangulation.h
   build-depends:
     base >= 4.20 && < 5
-    , moonlight-triangulation:ffi >= 1.0 && < 1.1
+    , moonlight-triangulation:ffi >= 1.2 && < 1.3
   ghc-options: -threaded
   if os(windows)
     options: standalone
@@ -332,9 +358,17 @@
   other-modules: Support
 
 common triangulation-native-test-slice
+  -- The focused milestone spec reaches into the dedicated @src-embedding@
+  -- root for exactly this one hidden certifier. Keeping that source root
+  -- singular prevents package modules from becoming duplicate test home modules.
+  hs-source-dirs: src-embedding
   other-modules:
     Moonlight.Triangulation.NativeSpec
     Moonlight.Triangulation.FilteredPredicateOptimizationSpec
+    Moonlight.Triangulation.ExactEmbeddingSpec
+    Moonlight.Triangulation.OverlaySpec
+    Moonlight.Triangulation.RegionSpec
+    Moonlight.Triangulation.Internal.Overlay.Embedding
   -- The RTS options this module needs are named on the SUITES rather than
   -- here, which is the one place the slice-owns-its-requirements rule cannot
   -- hold.  @-with-rtsopts@ is CONCATENATED by GHC across repetitions, so a
@@ -355,6 +389,7 @@
   build-depends:
     binary >= 0.8 && < 0.9
     , bytestring >= 0.12 && < 0.13
+    , moonlight-triangulation:dcel
     , moonlight-triangulation:serialize
 
 -- The operand meshes both algebra slices are stated over.  It sits at the
@@ -369,6 +404,28 @@
   other-modules: Moonlight.Triangulation.AlgebraSpec
   build-depends: containers >= 0.8 && < 0.9
 
+-- Exact planar Boolean laws, stated exclusively through the public facade.
+common triangulation-region-algebra-law-slice
+  other-modules: Moonlight.Triangulation.RegionAlgebraSpec
+  build-depends: containers >= 0.8 && < 0.9
+
+-- Exact intrinsic-volume laws over the resident cell carrier and published
+-- region view.
+common triangulation-valuation-law-slice
+  other-modules: Moonlight.Triangulation.ValuationSpec
+  build-depends:
+    containers >= 0.8 && < 0.9
+    , moonlight-triangulation:core
+    , moonlight-triangulation:dcel
+    , moonlight-triangulation:build
+
+common triangulation-minkowski-law-slice
+  other-modules: Moonlight.Triangulation.MinkowskiSpec
+  build-depends:
+    moonlight-triangulation:core
+    , moonlight-triangulation:dcel
+    , moonlight-triangulation:build
+
 -- The agreement between the seam schedule and the reference rebuild.  It names
 -- an internal schedule, so it reaches below the wall and travels with the
 -- schedule it names: a replacement kernel carries its own copy of this.
@@ -421,6 +478,9 @@
     triangulation-test-support-slice,
     triangulation-algebra-fixture-slice,
     triangulation-algebra-law-slice,
+    triangulation-region-algebra-law-slice,
+    triangulation-valuation-law-slice,
+    triangulation-minkowski-law-slice,
     triangulation-algebra-schedule-slice
   type: exitcode-stdio-1.0
   main-is: Main.hs
@@ -463,6 +523,9 @@
     triangulation-serialization-test-slice,
     triangulation-algebra-fixture-slice,
     triangulation-algebra-law-slice,
+    triangulation-region-algebra-law-slice,
+    triangulation-valuation-law-slice,
+    triangulation-minkowski-law-slice,
     triangulation-algebra-schedule-slice,
     triangulation-parallel-test-slice
   type: exitcode-stdio-1.0
@@ -519,6 +582,14 @@
     moonlight-triangulation:dcel
     , moonlight-triangulation:build
 
+common triangulation-region-benchmark-slice
+  other-modules: Moonlight.Triangulation.RegionBench
+  build-depends:
+    containers >= 0.8 && < 0.9
+    , moonlight-triangulation:core
+    , moonlight-triangulation:dcel
+    , moonlight-triangulation:build
+
 benchmark moonlight-triangulation-build-bench
   import:
     triangulation-benchmark-properties,
@@ -575,6 +646,17 @@
     bench/join
     bench/support
 
+benchmark moonlight-triangulation-region-bench
+  import:
+    triangulation-benchmark-properties,
+    triangulation-benchmark-support-slice,
+    triangulation-region-benchmark-slice
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  hs-source-dirs:
+    bench/region
+    bench/support
+
 benchmark moonlight-triangulation-bench
   import:
     triangulation-benchmark-properties,
@@ -582,7 +664,8 @@
     triangulation-build-benchmark-slice,
     triangulation-dcel-benchmark-slice,
     triangulation-dual-benchmark-slice,
-    triangulation-join-benchmark-slice
+    triangulation-join-benchmark-slice,
+    triangulation-region-benchmark-slice
   type: exitcode-stdio-1.0
   main-is: Main.hs
   hs-source-dirs:
@@ -591,4 +674,5 @@
     bench/dcel
     bench/dual
     bench/join
+    bench/region
     bench/support
diff --git a/src-build/Moonlight/Triangulation/BulkLoad.hs b/src-build/Moonlight/Triangulation/BulkLoad.hs
--- a/src-build/Moonlight/Triangulation/BulkLoad.hs
+++ b/src-build/Moonlight/Triangulation/BulkLoad.hs
@@ -3,6 +3,7 @@
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-}
 
 -- | Generation. @delaunay@ builds a mesh from a whole site set by circle sweep;
 -- the insertion verbs extend an existing mesh one site at a time.
@@ -24,6 +25,7 @@
 import Data.Primitive.PrimArray
   ( MutablePrimArray
   , newPrimArray
+  , readPrimArray
   , unsafeFreezePrimArray
   , writePrimArray
   )
@@ -46,7 +48,7 @@
   , newOperationState
   , setCounter
   )
-import Moonlight.Triangulation.Internal.CircleSweep (circleSweepInsert)
+import Moonlight.Triangulation.Internal.CircleSweep (circleSweepInsert, radiallyOrderArena)
 import Moonlight.Triangulation.Internal.PointIndex
   ( MutablePointIndex
   , emptyPointIndex
@@ -100,6 +102,13 @@
   = KeepFirstPayload
   | CombineDuplicatePayload !(vertex -> vertex -> vertex)
 
+-- | The local identity verdict for one input position. A fresh verdict carries
+-- the canonical coordinates already admitted to the mutable DCEL, so ingress
+-- never rereads its own writes merely to accumulate the sweep centre.
+data PositionClaim
+  = ResidentPosition !Int
+  | FreshPosition !Int !Double !Double
+
 -- | Build a finite Delaunay DCEL while preserving the first input payload at
 -- every duplicate position. The returned mapping relates every input slot to
 -- the canonical stored vertex.
@@ -109,8 +118,7 @@
   => ElementDefaults directed undirected face
   -> V.Vector vertex
   -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)
-delaunay defaults input = do
-  validateVertices input
+delaunay defaults input =
   buildDelaunayFromSource
     defaults
     (V.length input)
@@ -125,16 +133,124 @@
 delaunayGeometry
   :: V.Vector Point
   -> Either BuildError (DelaunayTriangulation ())
-delaunayGeometry coordinates = do
-  V.iforM_ coordinates (\index point -> validatePoint (Just index) point)
-  buildTriangulation
-    <$> buildDelaunayFromSource
+delaunayGeometry coordinates
+  | inputCount == 0 = Right (empty unitElementDefaults)
+  | otherwise = do
+    V.iforM_ coordinates (\index point -> () <$ validatePoint (Just index) point)
+    ensureCapacity inputCount
+    let !canonicalArena =
+          U.generate inputCount $ \index ->
+            case coordinates V.! index of
+              Point rawX rawY ->
+                ( 0
+                , canonicalCoordinate rawX
+                , canonicalCoordinate rawY
+                , fromIntegral index
+                )
+        (!inputSumX, !inputSumY) =
+          U.foldl'
+            (\(!sumX, !sumY) (_, x, y, _) -> (sumX + x, sumY + y))
+            (0, 0)
+            canonicalArena
+    runST $ do
+      inputArena <- U.unsafeThaw canonicalArena
+      let !inputScale = recip (fromIntegral inputCount)
+          !inputCenterX = inputSumX * inputScale
+          !inputCenterY = inputSumY * inputScale
+      assignGeometryRadialDistances inputArena inputCenterX inputCenterY
+      orderedInputArena <- radiallyOrderArena inputArena
+      defaultedMutable <-
+        newMutableDcelWithVertexDefault
+          ()
           unitElementDefaults
-          (V.length coordinates)
-          (coordinates V.!)
-          (const ())
-          KeepFirstPayload
+          (planarDcelCapacity inputCount)
+      let !mutable = defaultedVertexDcel defaultedMutable
+      operation <- newOperationState (halfEdgeCapacity mutable)
+      unique <- appendSortedGeometry defaultedMutable inputArena
+      orderedArena <-
+        if unique == inputCount
+          then pure orderedInputArena
+          else do
+            let !scale = recip (fromIntegral unique)
+                !uniqueArena = MUV.unsafeSlice 0 unique inputArena
+            (sumX, sumY) <- sumGeometryArena uniqueArena
+            recenterGeometryArena
+              (sumX * scale)
+              (sumY * scale)
+              uniqueArena
+            radiallyOrderArena uniqueArena
+      inserted <- circleSweepInsert mutable operation orderedArena
+      case inserted of
+        Left failure -> pure (Left failure)
+        Right _ -> freezeTriangulation mutable
+ where
+  !inputCount = V.length coordinates
 
+  assignGeometryRadialDistances
+    :: forall s
+     . MUV.MVector s (Double, Double, Double, Word32)
+    -> Double
+    -> Double
+    -> ST s ()
+  assignGeometryRadialDistances arena centerX centerY =
+    MUV.imapM_
+      (\index (_, x, y, input) -> do
+        let !deltaX = centerX - x
+            !deltaY = centerY - y
+        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, input)
+      )
+      arena
+
+  appendSortedGeometry
+    :: forall s
+     . DefaultedVertexDcel s () () () ()
+    -> MUV.MVector s (Double, Double, Double, Word32)
+    -> ST s Int
+  appendSortedGeometry defaultedMutable arena
+    | inputCount == 0 = pure 0
+    | otherwise = do
+        (distance, x, y, _) <- MUV.unsafeRead arena 0
+        first <- appendDefaultVertexCoordinates defaultedMutable x y
+        MUV.unsafeWrite arena 0 (distance, x, y, fromIntegral first)
+        (_, _, unique) <-
+          MUV.foldM'
+            (\(!previousX, !previousY, !uniqueCount) (nextDistance, nextX, nextY, _) ->
+               if nextX == previousX && nextY == previousY
+                 then pure (previousX, previousY, uniqueCount)
+                 else do
+                   vertex <- appendDefaultVertexCoordinates defaultedMutable nextX nextY
+                   MUV.unsafeWrite arena uniqueCount (nextDistance, nextX, nextY, fromIntegral vertex)
+                   pure (nextX, nextY, uniqueCount + 1)
+            )
+            (x, y, 1)
+            (MUV.unsafeSlice 1 (inputCount - 1) arena)
+        pure unique
+
+  sumGeometryArena
+    :: forall s
+     . MUV.MVector s (Double, Double, Double, Word32)
+    -> ST s (Double, Double)
+  sumGeometryArena arena =
+    MUV.foldM'
+      (\(!sumX, !sumY) (_, x, y, _) -> pure (sumX + x, sumY + y))
+      (0, 0)
+      arena
+
+  recenterGeometryArena
+    :: forall s
+     . Double
+    -> Double
+    -> MUV.MVector s (Double, Double, Double, Word32)
+    -> ST s ()
+  recenterGeometryArena centerX centerY arena =
+    MUV.imapM_
+      (\index (_, x, y, vertex) -> do
+        let !deltaX = centerX - x
+            !deltaY = centerY - y
+        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, vertex)
+      )
+      arena
+
 -- | Canonical construction from separate geometry and annotation sources.
 -- The coordinate vector remains the only geometry in ingress; payloads never
 -- acquire a fabricated 'HasPosition' instance merely to reach the loader.
@@ -148,8 +264,7 @@
 delaunayFromCoordinates defaults coordinates payloads duplicatePolicy
   | coordinateCount /= payloadCount =
       Left (CoordinatePayloadCountMismatch coordinateCount payloadCount)
-  | otherwise = do
-      V.iforM_ coordinates (\index point -> validatePoint (Just index) point)
+  | otherwise =
       buildDelaunayFromSource
         defaults
         coordinateCount
@@ -171,28 +286,42 @@
 buildDelaunayFromSource defaults inputCount pointAtInput payloadAtInput duplicatePolicy = do
   ensureCapacity inputCount
   runST $ do
-    mutable <- newMutableDcel defaults inputCount
-    operation <- newOperationState (halfEdgeCapacity mutable)
-    table <- newMutablePointIndex inputCount
-    mapping <- newPrimArray inputCount
-    ingressed <- ingress mutable operation table mapping 0 0 0
-    case ingressed of
+    inputArena <- MUV.new inputCount
+    admitted <- initializeInputArena inputArena 0 0 0
+    case admitted of
       Left failure -> pure (Left failure)
-      Right (sumX, sumY) -> do
-        unique <- pointCount mutable
+      Right (inputSumX, inputSumY) -> do
+        let !inputScale = if inputCount == 0 then 0 else recip (fromIntegral inputCount)
+            !inputCenterX = inputSumX * inputScale
+            !inputCenterY = inputSumY * inputScale
+        assignRadialDistances inputArena inputCenterX inputCenterY 0
+        orderedInputArena <- radiallyOrderArena inputArena
+        mutable <- newMutableDcel defaults (planarDcelCapacity inputCount)
+        operation <- newOperationState (halfEdgeCapacity mutable)
+        mapping <- newPrimArray inputCount
+        unique <- classifySortedInputs mapping inputArena
+        (sumX, sumY) <- appendClassifiedInputs mutable mapping 0 0 0
+        setCounter operation CounterInputPoints inputCount
+        setCounter operation CounterUniquePoints unique
+        setCounter operation CounterDuplicatePoints (inputCount - unique)
         inserted <-
           if unique == 0
             then pure (Right 0)
             else do
-              let !scale = recip (fromIntegral unique)
-              arena <-
-                fillRadialArena
-                  mutable
-                  (sumX * scale)
-                  (sumY * scale)
-                  (\index -> pure (fromIntegral index))
-                  unique
-              circleSweepInsert mutable operation arena
+              orderedArena <-
+                if unique == inputCount
+                  then pure orderedInputArena
+                  else do
+                    let !scale = recip (fromIntegral unique)
+                        !uniqueArena = MUV.unsafeSlice 0 unique inputArena
+                    rewriteCompactedArena
+                      mapping
+                      (sumX * scale)
+                      (sumY * scale)
+                      uniqueArena
+                      0
+                    radiallyOrderArena uniqueArena
+              circleSweepInsert mutable operation orderedArena
         case inserted of
           Left failure -> pure (Left failure)
           Right seedCount -> do
@@ -212,44 +341,138 @@
                         }
                   )
  where
-  -- One indexed ingress loop: read the position once, claim it against the
-  -- transient table, write the input mapping, and accumulate the sort centre
-  -- over the vertices that are actually new. No list, no decorated vector.
-  ingress
+  -- Validate, canonicalize, and materialize each source position in one local
+  -- section. The former validation pass and centre fold both traversed the
+  -- boxed source before arena generation traversed it a third time; this
+  -- section returns their only global invariant, the coordinate sum.
+  initializeInputArena
     :: forall s
-     . MutableDcel s vertex directed undirected face
-    -> OperationState s
-    -> MutablePointIndex s
-    -> MutablePrimArray s Word32
+     . MUV.MVector s (Double, Double, Double, Word32)
     -> Int
     -> Double
     -> Double
     -> ST s (Either BuildError (Double, Double))
-  ingress mutable operation table mapping !index !sumX !sumY
+  initializeInputArena arena !index !sumX !sumY
     | index >= inputCount = pure (Right (sumX, sumY))
+    | otherwise =
+        case validatePoint (Just index) (pointAtInput index) of
+          Left failure -> pure (Left failure)
+          Right admitted ->
+            case queryPointValue admitted of
+              Point x y -> do
+                MUV.unsafeWrite arena index (0, x, y, fromIntegral index)
+                initializeInputArena arena (index + 1) (sumX + x) (sumY + y)
+
+  -- The all-input centre seeds the first radial ordering. Duplicate
+  -- equivalence classes become adjacent in that total order; after descent,
+  -- the compacted section is recentered over unique sites and ordered once
+  -- more only when required.
+  assignRadialDistances
+    :: forall s
+     . MUV.MVector s (Double, Double, Double, Word32)
+    -> Double
+    -> Double
+    -> Int
+    -> ST s ()
+  assignRadialDistances arena centerX centerY !index
+    | index >= inputCount = pure ()
     | otherwise = do
-        addCounter operation CounterInputPoints 1
+        (_, x, y, input) <- MUV.unsafeRead arena index
+        let !deltaX = centerX - x
+            !deltaY = centerY - y
+        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, input)
+        assignRadialDistances arena centerX centerY (index + 1)
+
+  -- Classify the sorted local sections by exact canonical position. The
+  -- mapping first names each class by its earliest input slot. Compacting the
+  -- unique radial representatives in place cannot overwrite an unread slot.
+  classifySortedInputs
+    :: forall s
+     . MutablePrimArray s Word32
+    -> MUV.MVector s (Double, Double, Double, Word32)
+    -> ST s Int
+  classifySortedInputs mapping arena
+    | inputCount == 0 = pure 0
+    | otherwise = do
+        first@(_, firstX, firstY, firstInput) <- MUV.unsafeRead arena 0
+        writePrimArray mapping (fromIntegral firstInput) firstInput
+        classifyFrom first firstX firstY firstInput 1 1
+   where
+    classifyFrom !_ !previousX !previousY !classInput !readIndex !uniqueCount
+      | readIndex >= inputCount = pure uniqueCount
+      | otherwise = do
+          record@(_, x, y, input) <- MUV.unsafeRead arena readIndex
+          if x == previousX && y == previousY
+            then do
+              writePrimArray mapping (fromIntegral input) classInput
+              classifyFrom record previousX previousY classInput (readIndex + 1) uniqueCount
+            else do
+              writePrimArray mapping (fromIntegral input) input
+              if uniqueCount == readIndex
+                then pure ()
+                else MUV.unsafeWrite arena uniqueCount record
+              classifyFrom record x y input (readIndex + 1) (uniqueCount + 1)
+
+  -- Materialize in original order, preserving the established handle
+  -- assignment and duplicate-payload law. The equivalence mapping for a
+  -- duplicate always points backward to an already materialized class owner.
+  appendClassifiedInputs
+    :: forall s
+     . MutableDcel s vertex directed undirected face
+    -> MutablePrimArray s Word32
+    -> Int
+    -> Double
+    -> Double
+    -> ST s (Double, Double)
+  appendClassifiedInputs mutable mapping !index !sumX !sumY
+    | index >= inputCount = pure (sumX, sumY)
+    | otherwise = do
+        classInput <- readPrimArray mapping index
         let !vertexData = payloadAtInput index
-        claimed <- claimPosition mutable table (pointAtInput index) vertexData
-        case claimed of
-          Left failure -> pure (Left failure)
-          Right (vertex, fresh) -> do
-            writePrimArray mapping index (fromIntegral vertex)
-            if fresh
-              then do
-                addCounter operation CounterUniquePoints 1
-                x <- readPointX mutable vertex
-                y <- readPointY mutable vertex
-                ingress mutable operation table mapping (index + 1) (sumX + x) (sumY + y)
-              else do
-                case duplicatePolicy of
-                  KeepFirstPayload -> pure ()
-                  CombineDuplicatePayload combine -> do
-                    resident <- vertexDataAt mutable vertex
-                    writeVertexData mutable vertex (combine resident vertexData)
-                addCounter operation CounterDuplicatePoints 1
-                ingress mutable operation table mapping (index + 1) sumX sumY
+        if fromIntegral classInput == index
+          then case pointAtInput index of
+            Point x y -> do
+              let !canonicalX = canonicalCoordinate x
+                  !canonicalY = canonicalCoordinate y
+              vertex <- appendVertexCoordinates mutable canonicalX canonicalY vertexData
+              writePrimArray mapping index (fromIntegral vertex)
+              appendClassifiedInputs
+                mutable
+                mapping
+                (index + 1)
+                (sumX + canonicalX)
+                (sumY + canonicalY)
+          else do
+            resident <- fromIntegral <$> readPrimArray mapping (fromIntegral classInput)
+            writePrimArray mapping index (fromIntegral resident)
+            case duplicatePolicy of
+              KeepFirstPayload -> pure ()
+              CombineDuplicatePayload combine -> do
+                residentData <- vertexDataAt mutable resident
+                writeVertexData mutable resident (combine residentData vertexData)
+            appendClassifiedInputs mutable mapping (index + 1) sumX sumY
 
+  -- Once duplicates have shortened the vertex arena, translate each compacted
+  -- representative from its input-class name to its authoritative vertex and
+  -- restate its radial key around the exact unique-site centre.
+  rewriteCompactedArena
+    :: forall s
+     . MutablePrimArray s Word32
+    -> Double
+    -> Double
+    -> MUV.MVector s (Double, Double, Double, Word32)
+    -> Int
+    -> ST s ()
+  rewriteCompactedArena mapping centerX centerY arena !index
+    | index >= MUV.length arena = pure ()
+    | otherwise = do
+        (_, x, y, classInput) <- MUV.unsafeRead arena index
+        vertex <- readPrimArray mapping (fromIntegral classInput)
+        let !deltaX = centerX - x
+            !deltaY = centerY - y
+        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, vertex)
+        rewriteCompactedArena mapping centerX centerY arena (index + 1)
+
 -- | Insert or replace a vertex payload. A payload at an existing position is
 -- overwritten without changing topology.
 --
@@ -422,6 +645,11 @@
         case filled of
           Left failure -> pure (Left failure)
           Right (sumX, sumY, freshCount) -> do
+            let !existingCount = V.length input - freshCount
+            setCounter operation CounterInputPoints (V.length input)
+            setCounter operation CounterUniquePoints freshCount
+            setCounter operation CounterExistingPoints existingCount
+            setCounter operation CounterDuplicatePoints existingCount
             inserted <-
               if freshCount == 0
                 then pure (Right 0)
@@ -434,7 +662,8 @@
                       (sumY * scale)
                       (MUV.unsafeRead freshBuffer)
                       freshCount
-                  circleSweepInsert mutable operation arena
+                  orderedArena <- radiallyOrderArena arena
+                  circleSweepInsert mutable operation orderedArena
             case inserted of
               Left failure -> pure (Left failure)
               Right seedCount -> do
@@ -469,34 +698,27 @@
   fill mutable operation table mapping freshBuffer !index !sumX !sumY !freshCount
     | index >= V.length input = pure (Right (sumX, sumY, freshCount))
     | otherwise = do
-        addCounter operation CounterInputPoints 1
         let !vertexData = input V.! index
         claimed <- claimPosition mutable table (position vertexData) vertexData
         case claimed of
           Left failure -> pure (Left failure)
-          Right (vertex, fresh) -> do
+          Right (FreshPosition vertex canonicalX canonicalY) -> do
             writePrimArray mapping index (fromIntegral vertex)
-            if fresh
-              then do
-                addCounter operation CounterUniquePoints 1
-                MUV.unsafeWrite freshBuffer freshCount (fromIntegral vertex)
-                x <- readPointX mutable vertex
-                y <- readPointY mutable vertex
-                fill
-                  mutable
-                  operation
-                  table
-                  mapping
-                  freshBuffer
-                  (index + 1)
-                  (sumX + x)
-                  (sumY + y)
-                  (freshCount + 1)
-              else do
-                writeVertexData mutable vertex vertexData
-                addCounter operation CounterExistingPoints 1
-                addCounter operation CounterDuplicatePoints 1
-                fill mutable operation table mapping freshBuffer (index + 1) sumX sumY freshCount
+            MUV.unsafeWrite freshBuffer freshCount (fromIntegral vertex)
+            fill
+              mutable
+              operation
+              table
+              mapping
+              freshBuffer
+              (index + 1)
+              (sumX + canonicalX)
+              (sumY + canonicalY)
+              (freshCount + 1)
+          Right (ResidentPosition vertex) -> do
+            writePrimArray mapping index (fromIntegral vertex)
+            writeVertexData mutable vertex vertexData
+            fill mutable operation table mapping freshBuffer (index + 1) sumX sumY freshCount
 
 -- | One packed radial record per swept vertex — the derived sort fields and
 -- the vertex handle, nothing else — filled straight from the coordinate
@@ -530,13 +752,14 @@
   -> MutablePointIndex s
   -> Point
   -> vertex
-  -> ST s (Either BuildError (Int, Bool))
+  -> ST s (Either BuildError PositionClaim)
 claimPosition mutable table rawPoint vertexData =
   case rawPoint of
     Point x y -> do
       let !canonicalX = canonicalCoordinate x
           !canonicalY = canonicalCoordinate y
-      candidate <- pointCount mutable
+      slot <- nextVertexSlot mutable
+      let !candidate = nextVertexSlotIndex slot
       owner <-
         resolveMutablePoint
           table
@@ -547,10 +770,10 @@
           candidate
       case owner of
         Left failure -> pure (Left failure)
-        Right (Just existing) -> pure (Right (existing, False))
+        Right (Just existing) -> pure (Right (ResidentPosition existing))
         Right Nothing -> do
-          vertex <- appendVertexCoordinates mutable canonicalX canonicalY vertexData
-          pure (Right (vertex, True))
+          vertex <- appendVertexCoordinatesAtSlot mutable slot canonicalX canonicalY vertexData
+          pure (Right (FreshPosition vertex canonicalX canonicalY))
 -- | Enter the positions a batch inherits from the triangulation it extends, so
 -- that an input repeating one of them maps to the vertex already there.
 seedPointTable
diff --git a/src-build/Moonlight/Triangulation/Cdt.hs b/src-build/Moonlight/Triangulation/Cdt.hs
--- a/src-build/Moonlight/Triangulation/Cdt.hs
+++ b/src-build/Moonlight/Triangulation/Cdt.hs
@@ -44,7 +44,7 @@
   , constraintSegments
   , unionConstrainedWith
   , unionConstrained
-  , joinSeparatedConstrainedWith
+  , joinSeparatedConstrained
   , extendConstrainedWith
   , constraintStorageBytes
   , existsConstraint
@@ -111,7 +111,7 @@
 import Moonlight.Triangulation.Internal.Cdt.Union
   ( constraintSegments
   , extendConstrainedWith
-  , joinSeparatedConstrainedWith
+  , joinSeparatedConstrained
   , unionConstrained
   , unionConstrainedWith
   )
diff --git a/src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs b/src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs
--- a/src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs
+++ b/src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs
@@ -10,7 +10,7 @@
   , constraintSegments
   , unionConstrainedWith
   , unionConstrained
-  , joinSeparatedConstrainedWith
+  , joinSeparatedConstrained
   , extendConstrainedWith
   , segmentRequest
   , firstRejected
@@ -150,18 +150,15 @@
 -- immutable barriers rather than edges recovery tries to resurrect after a
 -- solved face has already disappeared.
 --
--- The annotation combiner is intentionally not evaluated: strict x-separation
--- proves that the two site sets have no coincident point. It remains in the
--- signature so this operation composes at the same annotation boundary as the
--- other constrained joins without inventing a geometry-only facade.
-joinSeparatedConstrainedWith
-  :: (annotation -> annotation -> annotation)
-  -> Triangulation 'Constrained annotation () () ()
+-- Strict x-separation proves that the two site sets have no coincident point,
+-- so annotations are copied from their source mesh and never combined.
+joinSeparatedConstrained
+  :: Triangulation 'Constrained annotation () () ()
   -> Triangulation 'Constrained annotation () () ()
   -> Either
       (ConstrainedUnionError)
       (ConstrainedSeamResult annotation)
-joinSeparatedConstrainedWith _combine left right = do
+joinSeparatedConstrained left right = do
   seamPlan <- maybe (Left ConstraintUnionNotSeparated) Right (planSeam left right)
   seamExecution <-
     mapLeft
diff --git a/src-build/Moonlight/Triangulation/Internal/CircleSweep.hs b/src-build/Moonlight/Triangulation/Internal/CircleSweep.hs
--- a/src-build/Moonlight/Triangulation/Internal/CircleSweep.hs
+++ b/src-build/Moonlight/Triangulation/Internal/CircleSweep.hs
@@ -2,41 +2,66 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-}
 
 module Moonlight.Triangulation.Internal.CircleSweep
-  ( circleSweepInsert
+  ( RadiallyOrderedArena
+  , radiallyOrderArena
+  , circleSweepInsert
   ) where
 
 import Control.Monad (forM_, when)
 import Control.Monad.ST (ST)
 import Data.Bits (xor)
-import Data.STRef
-  ( STRef
-  , newSTRef
-  , readSTRef
-  , writeSTRef
-  )
 import qualified Data.Vector.Algorithms.Intro as Intro
 import qualified Data.Vector.Unboxed.Mutable as MUV
 import Data.Word (Word32)
 import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..))
 import Moonlight.Triangulation.Insertion (insertExistingVertex)
 import Moonlight.Triangulation.Internal.DcelOperations
-  ( closeOuterTurn
-  , drainLegalization
+  ( ReservedSweepCells
+  , SweepCellCursor
+  , SweepInsertion (..)
+  , closeOuterTurnReserved
+  , commitReservedSweepConnections
   , fixHullConvexity
-  , insertOutsideHullBetween
-  , LegalizationLaw (..)
-  , noStarVertex
-  , seedGenericEdges
+  , initialSweepCellCursor
+  , insertOutsideHullAtEdge
+  , reserveSweepCells
   )
+import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
+  ( seedGenericPairInArena
+  )
+import Moonlight.Triangulation.Internal.DcelOperations.Normalize
+  ( LegalizationDrain (..)
+  , drainDenseUnconstrainedGenericLegalization
+  )
 import Moonlight.Triangulation.Internal.Mutable
+  ( DenseMutableDcel
+  , MutableDcel
+  , denseMutableDcel
+  , denseMutableOwner
+  , denseFaceEdges
+  , denseReadFaceEdge
+  , denseReadNext
+  , denseReadOrigin
+  , denseReadPointX
+  , denseReadPointY
+  , denseReadPrevious
+  , directedEdgeCount
+  , faceCount
+  , halfEdgeCapacity
+  , pointCapacity
+  )
 import Moonlight.Triangulation.Internal.OperationState
   ( Counter (..)
+  , LegalizationArena
   , OperationState
   , addCounter
+  , legalizationArena
   , maxCounter
   , readScratch
+  , storeLegalizationArena
   , writeScratch
   )
 import Moonlight.Triangulation.Internal.Probe (Probe (..))
@@ -59,18 +84,46 @@
   , hullCenterY :: {-# UNPACK #-} !Double
   , hullBucketCapacity :: {-# UNPACK #-} !Int
   , hullAngleByEdge :: !(MUV.MVector s Double)
-  , hullActiveCount :: !(MUV.MVector s Int)
-  , hullBuckets :: !(STRef s (MUV.MVector s Word32))
   }
 
-readActiveCount :: Hull s -> ST s Int
-readActiveCount hull = MUV.unsafeRead (hullActiveCount hull) 0
-{-# INLINE readActiveCount #-}
+-- | The strict state of the derived angular section. The bucket vector is
+-- mutated only to transport the section across one local hull rewrite; its
+-- extent and active cardinality are threaded as values, so the sweep does not
+-- bounce through singleton mutable cells for facts already known at descent.
+data HullIndex s = HullIndex
+  !(MUV.MVector s Word32)
+  {-# UNPACK #-} !Int
 
-writeActiveCount :: Hull s -> Int -> ST s ()
-writeActiveCount hull = MUV.unsafeWrite (hullActiveCount hull) 0
-{-# INLINE writeActiveCount #-}
+data DeferredInsertion s
+  = DeferredInsertionFailure !BuildError
+  | DeferredInsertionSuccess
+      {-# UNPACK #-} !Int
+      {-# UNPACK #-} !Int
+      {-# UNPACK #-} !SweepCellCursor
+      !(LegalizationArena s)
+      {-# UNPACK #-} !(HullIndex s)
 
+data ClosedHullSection s = ClosedHullSection
+  {-# UNPACK #-} !Int
+  {-# UNPACK #-} !Int
+  {-# UNPACK #-} !Int
+  !(LegalizationArena s)
+  {-# UNPACK #-} !SweepCellCursor
+
+-- | A packed sweep arena after its radial keys have descended to one total
+-- order. The constructor is private: the circle sweep consumes the proof and
+-- therefore never pays to establish the same ordering twice.
+newtype RadiallyOrderedArena s = RadiallyOrderedArena
+  (MUV.MVector s (Double, Double, Double, Word32))
+
+radiallyOrderArena
+  :: MUV.MVector s (Double, Double, Double, Word32)
+  -> ST s (RadiallyOrderedArena s)
+radiallyOrderArena arena = do
+  Intro.sort arena
+  pure (RadiallyOrderedArena arena)
+{-# INLINE radiallyOrderArena #-}
+
 noOuterEdge :: Word32
 noOuterEdge = maxBound
 
@@ -83,12 +136,22 @@
 circleSweepInsert
   :: MutableDcel s vertex directed undirected face
   -> OperationState s
+  -> RadiallyOrderedArena s
+  -> ST s (Either BuildError Int)
+circleSweepInsert mutable operation (RadiallyOrderedArena arena) =
+  case denseMutableDcel mutable of
+    Nothing -> pure (Left CircleSweepRequiresDenseStorage)
+    Just dense -> circleSweepInsertDense dense operation arena
+{-# INLINE circleSweepInsert #-}
+
+circleSweepInsertDense
+  :: DenseMutableDcel s vertex directed undirected face
+  -> OperationState s
   -> MUV.MVector s (Double, Double, Double, Word32)
   -> ST s (Either BuildError Int)
-circleSweepInsert mutable operation arena
+circleSweepInsertDense dense operation arena
   | MUV.length arena == 0 = pure (Right 0)
   | otherwise = do
-      Intro.sort arena
       let !ordered = MUV.length arena
       seed <- insertSeed 0
       case seed of
@@ -98,49 +161,59 @@
           if faces <= 1 || seedCount >= ordered
             then pure (Right seedCount)
             else do
-              (centerX, centerY) <- seedCentre mutable
-              builtHull <- buildHull mutable operation centerX centerY
-              case builtHull of
+              (centerX, centerY) <- seedCentre dense
+              reservedOutcome <- reserveSweepCells dense (ordered - seedCount)
+              case reservedOutcome of
                 Left failure -> pure (Left failure)
-                Right hull -> do
-                  skipped <- MUV.new (ordered - seedCount)
-                  inserted <- insertRemaining hull ordered seedCount skipped 0 0 0 0
-                  case inserted of
+                Right reserved -> do
+                  builtHull <- buildHull dense operation centerX centerY
+                  case builtHull of
                     Left failure -> pure (Left failure)
-                    Right (!skippedCount, !fastCount, !flips, !maxDepth) -> do
-                      -- The sweep counts its own hull insertions; its drains
-                      -- hand their flip and depth tallies up once.
-                      addCounter operation CounterHullInsertions fastCount
-                      -- The angular candidate leaves a point to the fallback
-                      -- whenever it lands right of, or on, the hull edge its
-                      -- own angle selected — spade's
-                      -- `is_on_right_side_or_on_line` branch, which spade's own
-                      -- source calls "very slow". Both counts ride out in
-                      -- BuildStats so the split is comparable across the two
-                      -- implementations and satisfies
-                      -- seed + fast + skipped = unique.
-                      --
-                      -- CounterSweepFastPoints is NOT CounterHullInsertions
-                      -- renamed: the latter is also charged by
-                      -- insertOutsideHull, so it counts hull-adjacent
-                      -- insertions from either path and dominates this one
-                      -- whenever a skipped point lands outside the hull. The
-                      -- two coincide only while skipped is zero, and charging
-                      -- them independently is what makes the identity a check
-                      -- rather than a restatement.
-                      addCounter operation CounterSweepFastPoints fastCount
-                      addCounter operation CounterSweepSkippedPoints skippedCount
-                      addCounter operation CounterEdgeFlips flips
-                      maxCounter operation CounterLegalizationMaxStack maxDepth
-                      repaired <- fixHullConvexity @'ProbeOff mutable operation
-                      case repaired of
+                    Right (!hull, !initialHullIndex) -> do
+                      skipped <- MUV.new (ordered - seedCount)
+                      initialLegalizationArena <- legalizationArena operation
+                      let !initialCursor = initialSweepCellCursor reserved
+                      inserted <- insertRemaining reserved initialCursor initialLegalizationArena hull initialHullIndex ordered seedCount skipped 0 0 0 0
+                      case inserted of
                         Left failure -> pure (Left failure)
-                        Right (!_closures, !terminalFlips, !terminalMaxDepth) -> do
-                          addCounter operation CounterEdgeFlips terminalFlips
-                          maxCounter operation CounterLegalizationMaxStack terminalMaxDepth
-                          insertedSkipped <- insertSkipped skipped skippedCount 0
-                          pure (seedCount <$ insertedSkipped)
+                        Right (!skippedCount, !fastCount, !flips, !maxDepth, !sweepCursor, !sweepArena) -> do
+                          repaired <- fixHullConvexity reserved sweepCursor operation sweepArena
+                          case repaired of
+                            Left failure -> pure (Left failure)
+                            Right (!_closures, !terminalFlips, !terminalMaxDepth, !finalArena, !finalCursor) -> do
+                              commitReservedSweepConnections reserved finalCursor fastCount
+                              storeLegalizationArena operation finalArena
+                              -- The sweep counts its own hull insertions; its drains
+                              -- hand their flip and depth tallies up once.
+                              addCounter operation CounterHullInsertions fastCount
+                          -- The angular candidate leaves a point to the fallback
+                          -- whenever it lands right of, or on, the hull edge its
+                          -- own angle selected — spade's
+                          -- `is_on_right_side_or_on_line` branch, which spade's own
+                          -- source calls "very slow". Both counts ride out in
+                          -- BuildStats so the split is comparable across the two
+                          -- implementations and satisfies
+                          -- seed + fast + skipped = unique.
+                          --
+                          -- CounterSweepFastPoints is NOT CounterHullInsertions
+                          -- renamed: the latter is also charged by
+                          -- insertOutsideHull, so it counts hull-adjacent
+                          -- insertions from either path and dominates this one
+                          -- whenever a skipped point lands outside the hull. The
+                          -- two coincide only while skipped is zero, and charging
+                          -- them independently is what makes the identity a check
+                          -- rather than a restatement.
+                              addCounter operation CounterSweepFastPoints fastCount
+                              addCounter operation CounterSweepSkippedPoints skippedCount
+                              addCounter operation CounterEdgeFlips flips
+                              maxCounter operation CounterLegalizationMaxStack maxDepth
+                              addCounter operation CounterEdgeFlips terminalFlips
+                              maxCounter operation CounterLegalizationMaxStack terminalMaxDepth
+                              insertedSkipped <- insertSkipped skipped skippedCount 0
+                              pure (seedCount <$ insertedSkipped)
  where
+  !mutable = denseMutableOwner dense
+
   insertSeed !index
     | index >= MUV.length arena = pure (Right index)
     | otherwise = do
@@ -154,30 +227,46 @@
               then pure (Right (index + 1))
               else insertSeed (index + 1)
 
-  insertRemaining !hull !ordered !index !skipped !skippedCount !fastCount !flips !maxDepth
-    | index >= ordered = pure (Right (skippedCount, fastCount, flips, maxDepth))
+  insertRemaining !reserved !cursor !candidateArena !hull !hullIndex !ordered !index !skipped !skippedCount !fastCount !flips !maxDepth
+    | index >= ordered = pure (Right (skippedCount, fastCount, flips, maxDepth, cursor, candidateArena))
     | otherwise = do
         (_, queryXWide, queryYWide, raw) <- MUV.unsafeRead arena index
         let !vertex = fromIntegral raw
-        queryX <- readPointX mutable vertex
-        queryY <- readPointY mutable vertex
         let !queryAngle =
               pseudoAngle (hullCenterX hull) (hullCenterY hull) queryXWide queryYWide
-        edge <- hullCandidate mutable hull queryAngle queryXWide queryYWide
-        fromVertex <- readOrigin mutable edge
-        toVertex <- readOrigin mutable (edge `xor` 1)
-        fromX <- readPointX mutable fromVertex
-        fromY <- readPointY mutable fromVertex
-        toX <- readPointX mutable toVertex
-        toY <- readPointY mutable toVertex
-        if orient2dCoordinates fromX fromY toX toY queryX queryY == GT
+        edge <- hullCandidate dense hull hullIndex queryAngle queryXWide queryYWide
+        fromVertex <- denseReadOrigin dense edge
+        toVertex <- denseReadOrigin dense (edge `xor` 1)
+        fromX <- denseReadPointX dense fromVertex
+        fromY <- denseReadPointY dense fromVertex
+        toX <- denseReadPointX dense toVertex
+        toY <- denseReadPointY dense toVertex
+        if orient2dCoordinates fromX fromY toX toY queryXWide queryYWide == GT
           then do
-            deferred <- insertDeferred mutable operation hull edge vertex queryAngle
+            deferred <-
+              insertDeferred
+                reserved
+                cursor
+                dense
+                hull
+                hullIndex
+                candidateArena
+                edge
+                fromVertex
+                toVertex
+                vertex
+                queryXWide
+                queryYWide
+                queryAngle
             case deferred of
-              Left failure -> pure (Left failure)
-              Right (!_newClosures, !newFlips, !newMaxDepth) ->
+              DeferredInsertionFailure failure -> pure (Left failure)
+              DeferredInsertionSuccess !newFlips !newMaxDepth !nextCursor !nextArena !nextHullIndex ->
                 insertRemaining
+                  reserved
+                  nextCursor
+                  nextArena
                   hull
+                  nextHullIndex
                   ordered
                   (index + 1)
                   skipped
@@ -187,7 +276,7 @@
                   (max maxDepth newMaxDepth)
           else do
             MUV.unsafeWrite skipped skippedCount raw
-            insertRemaining hull ordered (index + 1) skipped (skippedCount + 1) fastCount flips maxDepth
+            insertRemaining reserved cursor candidateArena hull hullIndex ordered (index + 1) skipped (skippedCount + 1) fastCount flips maxDepth
 
   insertSkipped skipped !count = go
    where
@@ -203,19 +292,19 @@
 -- | The hull centre: the centroid of the first inner face, in the widened
 -- comparison format, computed exactly as @centroid@ states it.
 seedCentre
-  :: MutableDcel s vertex directed undirected face
+  :: DenseMutableDcel s vertex directed undirected face
   -> ST s (Double, Double)
-seedCentre mutable = do
-  (e0, e1, e2) <- faceEdges mutable 1
-  o0 <- readOrigin mutable e0
-  o1 <- readOrigin mutable e1
-  o2 <- readOrigin mutable e2
-  x0 <- readPointX mutable o0
-  y0 <- readPointY mutable o0
-  x1 <- readPointX mutable o1
-  y1 <- readPointY mutable o1
-  x2 <- readPointX mutable o2
-  y2 <- readPointY mutable o2
+seedCentre dense = do
+  (e0, e1, e2) <- denseFaceEdges dense 1
+  o0 <- denseReadOrigin dense e0
+  o1 <- denseReadOrigin dense e1
+  o2 <- denseReadOrigin dense e2
+  x0 <- denseReadPointX dense o0
+  y0 <- denseReadPointY dense o0
+  x1 <- denseReadPointX dense o1
+  y1 <- denseReadPointY dense o1
+  x2 <- denseReadPointX dense o2
+  y2 <- denseReadPointY dense o2
   pure (x0 + (x1 - x0) / 3 + (x2 - x0) / 3, y0 + (y1 - y0) / 3 + (y2 - y0) / 3)
 
 -- | Index the authoritative outer cycle. The seed fan is star-shaped around
@@ -223,13 +312,14 @@
 -- cycle is already the angular order the predecessor search assumes; what
 -- remains is caching each edge's angle and anchoring the buckets.
 buildHull
-  :: MutableDcel s vertex directed undirected face
+  :: DenseMutableDcel s vertex directed undirected face
   -> OperationState s
   -> Double
   -> Double
-  -> ST s (Either BuildError (Hull s))
-buildHull mutable operation centerX centerY = do
-  countResult <- collectOuterEdges mutable operation
+  -> ST s (Either BuildError (Hull s, HullIndex s))
+buildHull dense operation centerX centerY = do
+  let !mutable = denseMutableOwner dense
+  countResult <- collectOuterEdges dense operation
   case countResult of
     Left failure -> pure (Left failure)
     Right count
@@ -237,33 +327,29 @@
       | otherwise -> do
           let !capacity = max (count + 4) (pointCapacity mutable + 8)
           hullAngleByEdge <- MUV.new (halfEdgeCapacity mutable)
-          hullActiveCount <- MUV.replicate 1 count
-          initialBuckets <- MUV.replicate (initialBucketCount count capacity) noOuterEdge
-          hullBuckets <- newSTRef initialBuckets
           let hull =
                 Hull
                   { hullCenterX = centerX
                   , hullCenterY = centerY
                   , hullBucketCapacity = capacity
                   , hullAngleByEdge
-                  , hullActiveCount
-                  , hullBuckets
                   }
           forM_ [0 .. count - 1] $ \index -> do
             edge <- readScratch operation index
-            origin <- readOrigin mutable edge
-            x <- readPointX mutable origin
-            y <- readPointY mutable origin
+            origin <- denseReadOrigin dense edge
+            x <- denseReadPointX dense origin
+            y <- denseReadPointY dense origin
             MUV.unsafeWrite hullAngleByEdge edge (pseudoAngle centerX centerY x y)
-            installBucketMaximum mutable hull edge
-          pure (Right hull)
+          hullIndex <- rebuildBuckets dense hull count (initialBucketCount count capacity)
+          pure (Right (hull, hullIndex))
 
 collectOuterEdges
-  :: MutableDcel s vertex directed undirected face
+  :: DenseMutableDcel s vertex directed undirected face
   -> OperationState s
   -> ST s (Either BuildError Int)
-collectOuterEdges mutable operation = do
-  start <- readFaceEdge mutable 0
+collectOuterEdges dense operation = do
+  let !mutable = denseMutableOwner dense
+  start <- denseReadFaceEdge dense 0
   if start < 0
     then pure (Right 0)
     else do
@@ -283,12 +369,12 @@
     | seen && edge == start = pure (Right count)
     | otherwise = do
         writeScratch operation count edge
-        following <- readNext mutable edge
+        following <- denseReadNext dense edge
         go (remaining - 1) start following True (count + 1)
 
 initialBucketCount :: Int -> Int -> Int
 initialBucketCount active capacity =
-  min capacity (nextPowerOfTwo (max 8 ((active + 7) `quot` 8)))
+  min capacity (nextPowerOfTwo (max 8 ((active + 1) `quot` 2)))
 
 nextPowerOfTwo :: Int -> Int
 nextPowerOfTwo requested = go 1
@@ -303,69 +389,66 @@
 readAngle hull edge = MUV.unsafeRead (hullAngleByEdge hull) edge
 {-# INLINE readAngle #-}
 
-rebuildBuckets :: MutableDcel s vertex directed undirected face -> Hull s -> Int -> ST s ()
-rebuildBuckets mutable hull requested = do
+rebuildBuckets :: DenseMutableDcel s vertex directed undirected face -> Hull s -> Int -> Int -> ST s (HullIndex s)
+rebuildBuckets dense hull active requested = do
   let !count = max 1 (min (hullBucketCapacity hull) requested)
   buckets <- MUV.replicate count noOuterEdge
-  writeSTRef (hullBuckets hull) buckets
-  active <- readActiveCount hull
-  start <- readFaceEdge mutable 0
+  start <- denseReadFaceEdge dense 0
   let go !remaining !edge
         | remaining <= 0 = pure ()
         | otherwise = do
-            installBucketMaximum mutable hull edge
-            following <- readNext mutable edge
+            following <- denseReadNext dense edge
+            angle <- readAngle hull edge
+            followingAngle <- readAngle hull following
+            writeBucketSegment buckets angle followingAngle edge
             go (remaining - 1) following
   when (active > 0 && start >= 0) (go active start)
+  pure (HullIndex buckets active)
 
-maybeGrowBuckets :: MutableDcel s vertex directed undirected face -> Hull s -> ST s ()
-maybeGrowBuckets mutable hull = do
-  active <- readActiveCount hull
-  buckets <- readSTRef (hullBuckets hull)
+maybeGrowBuckets :: DenseMutableDcel s vertex directed undirected face -> Hull s -> HullIndex s -> ST s (HullIndex s)
+maybeGrowBuckets dense hull hullIndex@(HullIndex buckets active) = do
   let !current = MUV.length buckets
-  when (active > 8 * current && current < hullBucketCapacity hull) $
-    rebuildBuckets mutable hull (min (hullBucketCapacity hull) (2 * current))
+  if active > 2 * current && current < hullBucketCapacity hull
+    then rebuildBuckets dense hull active (min (hullBucketCapacity hull) (2 * current))
+    else pure hullIndex
 
 bucketFor :: Int -> Double -> Int
 bucketFor count angle =
   min (count - 1) (max 0 (floor (angle * fromIntegral count * 0.25)))
 {-# INLINE bucketFor #-}
 
--- | Compare two outer edges by their origins' keys: cached angle first, then
--- the immutable origin coordinates, then the edge id. The coordinate reads
--- only happen on an exact angle tie, which is the same-ray case and no other.
-compareEdgeKeys
-  :: MutableDcel s vertex directed undirected face
-  -> Hull s
-  -> Int
+ceilingBucketFor :: Int -> Double -> Int
+ceilingBucketFor count angle =
+  ceiling (angle * fromIntegral count * 0.25) `rem` count
+{-# INLINE ceilingBucketFor #-}
+
+-- | Install the authoritative outer edge whose angular segment contains each
+-- bucket boundary in the half-open clockwise arc from @fromAngle@ to
+-- @toAngle@. Adjacency remains solely in the DCEL; this is the derived section
+-- needed to land a lookup near that ring.
+writeBucketSegment
+  :: MUV.MVector s Word32
+  -> Double
+  -> Double
   -> Int
-  -> ST s Ordering
-compareEdgeKeys mutable hull left right = do
-  leftAngle <- readAngle hull left
-  rightAngle <- readAngle hull right
-  case compare leftAngle rightAngle of
-    LT -> pure LT
-    GT -> pure GT
-    EQ -> do
-      leftOrigin <- readOrigin mutable left
-      rightOrigin <- readOrigin mutable right
-      leftX <- readPointX mutable leftOrigin
-      rightX <- readPointX mutable rightOrigin
-      case compare leftX rightX of
-        LT -> pure LT
-        GT -> pure GT
-        EQ -> do
-          leftY <- readPointY mutable leftOrigin
-          rightY <- readPointY mutable rightOrigin
-          case compare leftY rightY of
-            LT -> pure LT
-            GT -> pure GT
-            EQ -> pure (compare left right)
+  -> ST s ()
+writeBucketSegment buckets fromAngle toAngle edge = do
+  let !count = MUV.length buckets
+      !fromBucket = ceilingBucketFor count fromAngle
+      !toBucket = ceilingBucketFor count toAngle
+      !packed = fromIntegral edge
+  case compare fromBucket toBucket of
+    LT -> MUV.set (MUV.unsafeSlice fromBucket (toBucket - fromBucket) buckets) packed
+    GT -> do
+      MUV.set (MUV.unsafeSlice fromBucket (count - fromBucket) buckets) packed
+      MUV.set (MUV.unsafeSlice 0 toBucket buckets) packed
+    EQ -> pure ()
+{-# INLINE writeBucketSegment #-}
 
 -- | Whether an outer edge's key orders at or before the stated query key,
 -- settled field by field without materializing either key.
 edgeAtMost
-  :: MutableDcel s vertex directed undirected face
+  :: DenseMutableDcel s vertex directed undirected face
   -> Hull s
   -> Int
   -> Double
@@ -373,127 +456,141 @@
   -> Double
   -> Int
   -> ST s Bool
-edgeAtMost mutable hull edge queryAngle queryX queryY tie = do
+edgeAtMost dense hull edge queryAngle queryX queryY tie = do
   angle <- readAngle hull edge
   case compare angle queryAngle of
     LT -> pure True
     GT -> pure False
     EQ -> do
-      origin <- readOrigin mutable edge
-      x <- readPointX mutable origin
+      origin <- denseReadOrigin dense edge
+      x <- denseReadPointX dense origin
       case compare x queryX of
         LT -> pure True
         GT -> pure False
         EQ -> do
-          y <- readPointY mutable origin
+          y <- denseReadPointY dense origin
           case compare y queryY of
             LT -> pure True
             GT -> pure False
             EQ -> pure (edge <= tie)
 
-installBucketMaximum :: MutableDcel s vertex directed undirected face -> Hull s -> Int -> ST s ()
-installBucketMaximum mutable hull edge = do
-  buckets <- readSTRef (hullBuckets hull)
-  angle <- readAngle hull edge
-  let !bucket = bucketFor (MUV.length buckets) angle
-  currentRaw <- MUV.unsafeRead buckets bucket
-  if currentRaw == noOuterEdge
-    then MUV.unsafeWrite buckets bucket (fromIntegral edge)
-    else do
-      verdict <- compareEdgeKeys mutable hull edge (fromIntegral currentRaw)
-      when (verdict == GT) (MUV.unsafeWrite buckets bucket (fromIntegral edge))
-
--- | Drop @edge@ from the bucket index. When it was its bucket's maximum, the
--- cyclic predecessor @left@ stands in provided it still belongs to the same
--- bucket — exact, because a bucket's edges form one contiguous arc of the
--- angular order. The caller states @left@ explicitly: the mesh has already
--- been mutated by the time the index is updated, so the retired edge no
--- longer knows its own neighbour.
-removeBucketMaximum :: Hull s -> Int -> Int -> ST s ()
-removeBucketMaximum hull edge left = do
-  buckets <- readSTRef (hullBuckets hull)
-  angle <- readAngle hull edge
-  let !bucket = bucketFor (MUV.length buckets) angle
-  current <- MUV.unsafeRead buckets bucket
-  when (current == fromIntegral edge) $ do
-    leftAngle <- readAngle hull left
-    if left /= edge && bucketFor (MUV.length buckets) leftAngle == bucket
-      then MUV.unsafeWrite buckets bucket (fromIntegral left)
-      else MUV.unsafeWrite buckets bucket noOuterEdge
-
-retireEdge :: Hull s -> Int -> Int -> ST s ()
-retireEdge hull edge left = do
-  removeBucketMaximum hull edge left
-  active <- readActiveCount hull
-  writeActiveCount hull (active - 1)
-{-# INLINE retireEdge #-}
-
-activateEdge :: MutableDcel s vertex directed undirected face -> Hull s -> Int -> ST s ()
-activateEdge mutable hull edge = do
-  active <- readActiveCount hull
-  writeActiveCount hull (active + 1)
-  installBucketMaximum mutable hull edge
-  maybeGrowBuckets mutable hull
+-- | Reconcile the derived angular index once after the local topology section
+-- has glued. Replacing one outer edge by two adds one active edge; every closed
+-- turn removes one. The final two edges cover the entire rewritten angular
+-- arc, so their segments descend to the bucket view in one gluing step.
+finishHullRewrite
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Hull s
+  -> HullIndex s
+  -> Int
+  -> Int
+  -> Int
+  -> ST s (HullIndex s)
+finishHullRewrite dense hull (HullIndex buckets active) leftEdge rightEdge activeDelta = do
+  leftAngle <- readAngle hull leftEdge
+  middleAngle <- readAngle hull rightEdge
+  afterRight <- denseReadNext dense rightEdge
+  rightAngle <- readAngle hull afterRight
+  writeBucketSegment buckets leftAngle middleAngle leftEdge
+  writeBucketSegment buckets middleAngle rightAngle rightEdge
+  maybeGrowBuckets dense hull (HullIndex buckets (active + activeDelta))
+{-# INLINE finishHullRewrite #-}
 
 -- | The outer edge whose key is the greatest key at or below the query: the
 -- visible candidate the sweep inserts against. Bucket anchors land the walk
 -- near the answer and the live outer cycle carries it the rest of the way.
 hullCandidate
-  :: MutableDcel s vertex directed undirected face
+  :: DenseMutableDcel s vertex directed undirected face
   -> Hull s
+  -> HullIndex s
   -> Double
   -> Double
   -> Double
   -> ST s Int
-hullCandidate mutable hull queryAngle queryX queryY = do
-  buckets <- readSTRef (hullBuckets hull)
+hullCandidate dense hull (HullIndex buckets active) queryAngle queryX queryY = do
   let !count = MUV.length buckets
       !bucket = bucketFor count queryAngle
   raw <- MUV.unsafeRead buckets bucket
   if raw == noOuterEdge
-    then previousNonEmpty buckets (if bucket == 0 then count - 1 else bucket - 1) count
-    else adjustWithinBucket buckets bucket (fromIntegral raw)
+    then denseReadFaceEdge dense 0
+    else adjustFromBoundary bucket (fromIntegral raw)
  where
-  previousNonEmpty buckets !bucket !remaining
-    | remaining <= 0 = readFaceEdge mutable 0
-    | otherwise = do
-        raw <- MUV.unsafeRead buckets bucket
-        if raw /= noOuterEdge
-          then pure (fromIntegral raw)
-          else previousNonEmpty buckets (if bucket == 0 then MUV.length buckets - 1 else bucket - 1) (remaining - 1)
-
-  adjustWithinBucket buckets !bucket !initial = do
-    active <- readActiveCount hull
-    go active initial
+  adjustFromBoundary !bucket !initial = do
+    initialAtMost <- cyclicAtMost bucket initial
+    if initialAtMost
+      then advance active initial
+      else retreat active initial
    where
-    go !remaining !edge
+    cyclicAtMost boundaryBucket edge = do
+      angle <- readAngle hull edge
+      if boundaryBucket == 0 && angle > queryAngle
+        then pure True
+        else edgeAtMost dense hull edge queryAngle queryX queryY maxBound
+
+    advance !remaining !edge
       | remaining <= 0 = pure initial
       | otherwise = do
-          atMost <- edgeAtMost mutable hull edge queryAngle queryX queryY maxBound
-          if atMost
-            then pure edge
-            else do
-              left <- readPrevious mutable edge
-              leftAngle <- readAngle hull left
-              if bucketFor (MUV.length buckets) leftAngle /= bucket
-                then pure left
-                else go (remaining - 1) left
+          following <- denseReadNext dense edge
+          edgeAngle <- readAngle hull edge
+          followingAngle <- readAngle hull following
+          -- Only the bucket-zero anchor may precede the query by crossing the
+          -- angular seam. Once the walk leaves that anchor, ordinary key order
+          -- is authoritative; treating every high-angle edge as below a
+          -- bucket-zero query walks straight past the answer and around the
+          -- entire ring.
+          let crossesSeam = followingAngle < edgeAngle
+              seamPermitted = bucket == 0 && edgeAngle > queryAngle
+          followingAtMost <- edgeAtMost dense hull following queryAngle queryX queryY maxBound
+          if (not crossesSeam || seamPermitted) && followingAtMost
+            then advance (remaining - 1) following
+            else pure edge
 
+    retreat !remaining !edge
+      | remaining <= 0 = pure initial
+      | otherwise = do
+          previous <- denseReadPrevious dense edge
+          previousAtMost <- cyclicAtMost bucket previous
+          if previousAtMost
+            then advance remaining previous
+            else retreat (remaining - 1) previous
+
 insertDeferred
-  :: MutableDcel s vertex directed undirected face
-  -> OperationState s
+  :: ReservedSweepCells s vertex directed undirected face
+  -> SweepCellCursor
+  -> DenseMutableDcel s vertex directed undirected face
   -> Hull s
+  -> HullIndex s
+  -> LegalizationArena s
   -> Int
   -> Int
+  -> Int
+  -> Int
   -> Double
-  -> ST s (Either BuildError (Int, Int, Int))
-insertDeferred mutable operation hull replacedEdge vertex insertedAngle = do
-  inserted <- insertOutsideHullBetween @'ProbeOff mutable operation replacedEdge replacedEdge vertex
+  -> Double
+  -> Double
+  -> ST s (DeferredInsertion s)
+insertDeferred reserved cursor dense hull hullIndex arena replacedEdge fromVertex toVertex vertex insertedX insertedY insertedAngle = do
+  inserted <-
+    insertOutsideHullAtEdge
+      reserved
+      cursor
+      arena
+      replacedEdge
+      fromVertex
+      toVertex
+      vertex
   case inserted of
-    Left failure -> pure (Left failure)
-    Right (firstEdge, lastEdge) -> insertDeferredBetween firstEdge lastEdge
+    SweepInsertionFailure failure -> pure (DeferredInsertionFailure failure)
+    SweepInsertion firstOuterEdge lastOuterEdge initialFlips initialMaxDepth arenaAfterInsertion cursorAfterInsertion ->
+      insertDeferredBetween
+        firstOuterEdge
+        lastOuterEdge
+        initialFlips
+        initialMaxDepth
+        arenaAfterInsertion
+        cursorAfterInsertion
  where
-  insertDeferredBetween firstEdge lastEdge = do
+  insertDeferredBetween firstEdge lastEdge initialFlips initialMaxDepth arenaAfterInsertion cursorAfterInsertion = do
     -- The patch proves the new keys algebraically, so no key is rediscovered
     -- from the mesh: replacing outer edge a->b with a->v, v->b keeps the
     -- replaced edge's origin for the first spoke (its cached angle carries
@@ -501,108 +598,126 @@
     -- one the candidate search was already given. The tie-break is each fresh
     -- edge id itself.
     replacedAngle <- readAngle hull replacedEdge
-    leftOfReplaced <- readPrevious mutable firstEdge
-    retireEdge hull replacedEdge leftOfReplaced
     -- Both spokes join the outer cycle before either is indexed, and a bucket
     -- rebuild during activation walks the live cycle: both angle slots must be
     -- initialized before the first activation can read them.
     MUV.unsafeWrite (hullAngleByEdge hull) firstEdge replacedAngle
     MUV.unsafeWrite (hullAngleByEdge hull) lastEdge insertedAngle
-    activateEdge mutable hull firstEdge
-    activateEdge mutable hull lastEdge
     -- Every turn this insertion closes legalizes together in one epoch. Closure
     -- never deletes an edge and never touches the outer cycle, so which turns
     -- close does not depend on when the interior is repaired; the fan above
     -- keeps its own drain because its candidates are oriented against the
     -- inserted vertex, and that star must still be intact when they are tested.
-    leftOutcome <- closeLeft firstEdge 0 0
-    case leftOutcome of
-      Left obstruction -> pure (Left obstruction)
-      Right (_left, closuresLeft, topLeft) -> do
-        rightOutcome <- closeRight lastEdge 0 topLeft
-        case rightOutcome of
-          Left obstruction -> pure (Left obstruction)
-          Right (_right, closuresRight, topAll) -> do
-            (flips, maxDepth) <- drainLegalization @'ProbeOff mutable operation topAll noStarVertex ValidMesh
-            pure (Right (closuresLeft + closuresRight, flips, maxDepth))
+    ClosedHullSection left closuresLeft topLeft arenaAfterLeft cursorAfterLeft <- closeLeft firstEdge 0 0 arenaAfterInsertion cursorAfterInsertion
+    ClosedHullSection right closuresRight topAll arenaAfterRight cursorAfterRight <- closeRight lastEdge 0 topLeft arenaAfterLeft cursorAfterLeft
+    let !closures = closuresLeft + closuresRight
+    nextHullIndex <- finishHullRewrite dense hull hullIndex left right (1 - closures)
+    (!flips, !maxDepth, !finalArena) <-
+      if topAll == 0
+        then pure (0, 0, arenaAfterRight)
+        else do
+          LegalizationDrain drainedFlips drainedMaxDepth () drainedArena <-
+            drainDenseUnconstrainedGenericLegalization dense arenaAfterRight topAll
+          pure (drainedFlips, drainedMaxDepth, drainedArena)
+    pure
+      ( DeferredInsertionSuccess
+          (initialFlips + flips)
+          (max initialMaxDepth maxDepth)
+          cursorAfterRight
+          finalArena
+          nextHullIndex
+      )
 
-  closeLeft !current !closures !top = do
-    left <- readPrevious mutable current
-    close <- shouldCloseTurn mutable hull insertedAngle left current
+  closeLeft !current !closures !top !sectionArena !sectionCursor = do
+    left <- denseReadPrevious dense current
+    close <- shouldCloseLeftTurn dense hull insertedAngle insertedX insertedY left current
     if not close
-      then pure (Right (current, closures, top))
+      then pure (ClosedHullSection current closures top sectionArena sectionCursor)
       else do
         leftAngle <- readAngle hull left
-        closed <- closeOuterTurn mutable left
-        case closed of
-          Left obstruction -> pure (Left obstruction)
-          Right replacement -> do
-            -- Closing consecutive a->b, b->c into a->c keeps the first edge's
-            -- origin, so the replacement inherits its angle; the tie is the fresh
-            -- edge id. Both retired edges answer to the same left neighbour, the
-            -- edge now preceding the replacement on the outer cycle. Seeding
-            -- happens here, after the replacement's links exist.
-            leftOfReplacement <- readPrevious mutable replacement
-            retireEdge hull left leftOfReplacement
-            retireEdge hull current leftOfReplacement
-            MUV.unsafeWrite (hullAngleByEdge hull) replacement leftAngle
-            activateEdge mutable hull replacement
-            nextTop <- seedGenericEdges operation top [left, current]
-            closeLeft replacement (closures + 1) nextTop
+        (replacement, nextCursor) <- closeOuterTurnReserved reserved sectionCursor left
+        -- Closing consecutive a->b, b->c into a->c keeps the first edge's
+        -- origin, so the replacement inherits its angle; the tie is the fresh
+        -- edge id. Both retired edges answer to the same left neighbour, the
+        -- edge now preceding the replacement on the outer cycle. Seeding
+        -- happens here, after the replacement's links exist.
+        MUV.unsafeWrite (hullAngleByEdge hull) replacement leftAngle
+        (nextArena, nextTop) <- seedGenericPairInArena sectionArena top left current
+        closeLeft replacement (closures + 1) nextTop nextArena nextCursor
 
-  closeRight !current !closures !top = do
-    right <- readNext mutable current
-    close <- shouldCloseTurn mutable hull insertedAngle current right
+  closeRight !current !closures !top !sectionArena !sectionCursor = do
+    right <- denseReadNext dense current
+    close <- shouldCloseRightTurn dense hull insertedAngle insertedX insertedY current right
     if not close
-      then pure (Right (current, closures, top))
+      then pure (ClosedHullSection current closures top sectionArena sectionCursor)
       else do
         currentAngle <- readAngle hull current
-        closed <- closeOuterTurn mutable current
-        case closed of
-          Left obstruction -> pure (Left obstruction)
-          Right replacement -> do
-            leftOfReplacement <- readPrevious mutable replacement
-            retireEdge hull current leftOfReplacement
-            retireEdge hull right leftOfReplacement
-            MUV.unsafeWrite (hullAngleByEdge hull) replacement currentAngle
-            activateEdge mutable hull replacement
-            nextTop <- seedGenericEdges operation top [current, right]
-            closeRight replacement (closures + 1) nextTop
+        (replacement, nextCursor) <- closeOuterTurnReserved reserved sectionCursor current
+        MUV.unsafeWrite (hullAngleByEdge hull) replacement currentAngle
+        (nextArena, nextTop) <- seedGenericPairInArena sectionArena top current right
+        closeRight replacement (closures + 1) nextTop nextArena nextCursor
 {-# INLINE insertDeferred #-}
 
-shouldCloseTurn
-  :: MutableDcel s vertex directed undirected face
+-- | Test the left-hand turn where the inserted point is the target of the
+-- second edge. Adjacency is not rediscovered: 'closeLeft' obtained @first@
+-- from @previous second@ in the same local section.
+shouldCloseLeftTurn
+  :: DenseMutableDcel s vertex directed undirected face
   -> Hull s
   -> Double
+  -> Double
+  -> Double
   -> Int
   -> Int
   -> ST s Bool
-shouldCloseTurn mutable hull insertedAngle first second = do
-  following <- readNext mutable first
-  if following /= second
+shouldCloseLeftTurn dense hull insertedAngle insertedX insertedY first second = do
+  fromVertex <- denseReadOrigin dense first
+  middleVertex <- denseReadOrigin dense (first `xor` 1)
+  fromX <- denseReadPointX dense fromVertex
+  fromY <- denseReadPointY dense fromVertex
+  middleX <- denseReadPointX dense middleVertex
+  middleY <- denseReadPointY dense middleVertex
+  if orient2dWide fromX fromY middleX middleY insertedX insertedY /= GT
     then pure False
     else do
-      fromVertex <- readOrigin mutable first
-      middleVertex <- readOrigin mutable (first `xor` 1)
-      targetVertex <- readOrigin mutable (second `xor` 1)
-      fromX <- readPointX mutable fromVertex
-      fromY <- readPointY mutable fromVertex
-      middleX <- readPointX mutable middleVertex
-      middleY <- readPointY mutable middleVertex
-      targetX <- readPointX mutable targetVertex
-      targetY <- readPointY mutable targetVertex
-      if orient2dWide fromX fromY middleX middleY targetX targetY /= GT
-        then pure False
-        else do
-          -- The second edge begins at the middle vertex and stands on the outer
-          -- cycle, so its cached key already is that vertex's pseudo-angle;
-          -- same-ray is a slot read rather than a second angle.
-          middleAngle <- readAngle hull second
-          pure
-            ( middleAngle == insertedAngle
-                || acuteAtMiddle fromX fromY middleX middleY targetX targetY
-            )
-{-# INLINE shouldCloseTurn #-}
+      -- The second edge begins at the middle vertex and stands on the outer
+      -- cycle, so its cached key already is that vertex's pseudo-angle;
+      -- same-ray is a slot read rather than a second angle.
+      middleAngle <- readAngle hull second
+      pure
+        ( middleAngle == insertedAngle
+            || acuteAtMiddle fromX fromY middleX middleY insertedX insertedY
+        )
+{-# INLINE shouldCloseLeftTurn #-}
+
+-- | The symmetric right-hand test, where the inserted point is the first
+-- edge's source. 'closeRight' obtained @second@ from @next first@, so this
+-- section likewise consumes that adjacency proof instead of reading it again.
+shouldCloseRightTurn
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Hull s
+  -> Double
+  -> Double
+  -> Double
+  -> Int
+  -> Int
+  -> ST s Bool
+shouldCloseRightTurn dense hull insertedAngle insertedX insertedY first second = do
+  middleVertex <- denseReadOrigin dense (first `xor` 1)
+  targetVertex <- denseReadOrigin dense (second `xor` 1)
+  middleX <- denseReadPointX dense middleVertex
+  middleY <- denseReadPointY dense middleVertex
+  targetX <- denseReadPointX dense targetVertex
+  targetY <- denseReadPointY dense targetVertex
+  if orient2dWide insertedX insertedY middleX middleY targetX targetY /= GT
+    then pure False
+    else do
+      middleAngle <- readAngle hull second
+      pure
+        ( middleAngle == insertedAngle
+            || acuteAtMiddle insertedX insertedY middleX middleY targetX targetY
+        )
+{-# INLINE shouldCloseRightTurn #-}
 
 -- The deferred turn test deliberately runs the widened Binary64 predicate
 -- rather than the exact binary64 one; only the terminal Graham pass owns exact
diff --git a/src-build/Moonlight/Triangulation/Internal/Join/Seam.hs b/src-build/Moonlight/Triangulation/Internal/Join/Seam.hs
--- a/src-build/Moonlight/Triangulation/Internal/Join/Seam.hs
+++ b/src-build/Moonlight/Triangulation/Internal/Join/Seam.hs
@@ -334,7 +334,7 @@
       BuildError
       (Triangulation outputMode vertex () () (), BuildStats)
 mergeSeparated constraintSections left right (SeamTangents lowerLeft lowerRight upperLeft upperRight) = runST $ do
-  mutable <- newMutableDcel unitElementDefaults totalVertices
+  mutable <- newMutableDcel unitElementDefaults (generalDcelCapacity totalVertices)
   pointCapacityOutcome <- ensurePointCapacity mutable totalVertices
   cellCapacityOutcome <-
     ensureCellCapacity
diff --git a/src-build/Moonlight/Triangulation/Internal/Minkowski/Convex.hs b/src-build/Moonlight/Triangulation/Internal/Minkowski/Convex.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Internal/Minkowski/Convex.hs
@@ -0,0 +1,325 @@
+-- | Pure exact convex-polygon algebra: admission, linear edge-angle
+-- convolution, reflection, hull construction, and support-half-plane erosion.
+module Moonlight.Triangulation.Internal.Minkowski.Convex
+  ( convexPolygon
+  , convexPolygonPoints
+  , convexPolygonRegion
+  , admittedConvexLoop
+  , structuringElement
+  , structuringElementPolygon
+  , convexMinkowskiSum
+  , convexMinkowskiPolygon
+  , convexHullPolygon
+  , reflectConvexPolygon
+  , convexPolygonCentroid
+  , erodeConvexBy
+  , addExactPoints
+  , subtractExactPoints
+  ) where
+
+import Control.Monad (foldM)
+import Data.Bifunctor (first)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Set as Set
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , ExactVector (..)
+  , addExactVectors
+  , compareExactVectorAngle
+  , exactVectorFromPoints
+  , exactSegment
+  , exactSupportingLineIntersection
+  , exactOrient2d
+  , exactPoint
+  , exactPointCoordinates
+  , translateExactPoint
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle
+  ( cyclePairs
+  , cyclePairsNonEmpty
+  , cyclicTriples
+  , rotateCycleLeast
+  , rotateCycleLeastBy
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational
+  , exactDivide
+  )
+import Moonlight.Triangulation.Internal.Minkowski.Types
+  ( ConvexPolygon (..)
+  , MinkowskiError (..)
+  , StructuringElement (..)
+  )
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop (..)
+  , PlanarRegion (..)
+  , PolygonComponent (..)
+  , RegionPointLocation (..)
+  )
+import Moonlight.Triangulation.Region
+  ( exactLoop
+  , exactLoopPoints
+  , regionPointLocation
+  )
+
+convexPolygon
+  :: NonEmpty ExactPoint
+  -> Either MinkowskiError ConvexPolygon
+convexPolygon submitted = do
+  loop <- first MinkowskiInvalidConvexLoop (exactLoop submitted)
+  let points = exactLoopPoints loop
+  case
+    [ (index, turn)
+    | (index, (previous, current, next)) <-
+        zip [0 :: Int ..] (cyclicTriples (NonEmpty.toList points))
+    , let turn = exactOrient2d previous current next
+    , turn /= GT
+    ] of
+    (index, turn) : _ -> Left (MinkowskiNonConvexTurn index turn)
+    [] -> Right (ConvexPolygon loop)
+
+convexPolygonPoints :: ConvexPolygon -> NonEmpty ExactPoint
+convexPolygonPoints (ConvexPolygon loop) = exactLoopPoints loop
+
+convexPolygonRegion :: ConvexPolygon -> PlanarRegion
+convexPolygonRegion (ConvexPolygon loop) =
+  PlanarRegion [PolygonComponent loop []]
+
+admittedConvexLoop :: ExactLoop -> Maybe ConvexPolygon
+admittedConvexLoop loop
+  | all ((== GT) . orderedTurn) (cyclicTriples (NonEmpty.toList (exactLoopPoints loop))) =
+      Just (ConvexPolygon loop)
+  | otherwise = Nothing
+ where
+  orderedTurn (previous, current, next) =
+    exactOrient2d previous current next
+
+structuringElement
+  :: ConvexPolygon
+  -> Either MinkowskiError StructuringElement
+structuringElement polygon =
+  let origin = exactPoint 0 0
+      location = regionPointLocation (convexPolygonRegion polygon) origin
+   in case location of
+        RegionExterior -> Left (MinkowskiOriginOutside location)
+        _ -> Right (StructuringElement polygon)
+
+structuringElementPolygon :: StructuringElement -> ConvexPolygon
+structuringElementPolygon (StructuringElement polygon) = polygon
+
+convexMinkowskiSum
+  :: ConvexPolygon
+  -> ConvexPolygon
+  -> PlanarRegion
+convexMinkowskiSum left right =
+  convexPolygonRegion (convexMinkowskiPolygon left right)
+
+convexMinkowskiPolygon
+  :: ConvexPolygon
+  -> ConvexPolygon
+  -> ConvexPolygon
+convexMinkowskiPolygon left right =
+  let leftPoints = rotateCycleLeastBy pointSweepKey (convexPolygonPoints left)
+      rightPoints = rotateCycleLeastBy pointSweepKey (convexPolygonPoints right)
+      start = addExactPoints (NonEmpty.head leftPoints) (NonEmpty.head rightPoints)
+      directions =
+        mergeDirections
+          (edgeDirections leftPoints)
+          (edgeDirections rightPoints)
+      directionList = NonEmpty.toList directions
+      scanned = scanl translateExactPoint start directionList
+      resultPoints = start :| take (length directionList - 1) (drop 1 scanned)
+   in ConvexPolygon (ExactLoop (rotateCycleLeast resultPoints))
+
+convexHullPolygon
+  :: NonEmpty ExactPoint
+  -> Either MinkowskiError ConvexPolygon
+convexHullPolygon submitted =
+  let points = NonEmpty.toList submitted
+   in case convexHullPoints points of
+    Nothing -> Left (MinkowskiConvexHullDegenerate points)
+    Just hullPoints -> Right (ConvexPolygon (ExactLoop (rotateCycleLeast hullPoints)))
+
+reflectConvexPolygon :: ConvexPolygon -> ConvexPolygon
+reflectConvexPolygon polygon =
+  ConvexPolygon
+    ( ExactLoop
+        ( rotateCycleLeast
+            (NonEmpty.reverse (fmap negateExactPoint (convexPolygonPoints polygon)))
+        )
+    )
+
+convexPolygonCentroid
+  :: ConvexPolygon
+  -> Either MinkowskiError ExactPoint
+convexPolygonCentroid polygon = do
+  let points = convexPolygonPoints polygon
+      count = fromIntegral (NonEmpty.length points)
+      (sumX, sumY) =
+        foldl'
+          (\(accumulatedX, accumulatedY) point ->
+             let (x, y) = exactPointCoordinates point
+              in (accumulatedX + x, accumulatedY + y))
+          (0, 0)
+          points
+  x <- first MinkowskiExactArithmetic (exactDivide sumX count)
+  y <- first MinkowskiExactArithmetic (exactDivide sumY count)
+  pure (exactPoint x y)
+
+-- | Erode one convex polygon by another through the strongest translated
+-- support half-plane for each source edge. Sutherland--Hodgman descent keeps
+-- the construction exact; a lower-dimensional residual is represented by the
+-- empty polygonal region at this two-dimensional publication boundary.
+erodeConvexBy
+  :: ConvexPolygon
+  -> ConvexPolygon
+  -> Either MinkowskiError (Maybe ConvexPolygon)
+erodeConvexBy source kernel = do
+  let sourcePoints = convexPolygonPoints source
+      kernelPoints = convexPolygonPoints kernel
+      firstKernel = NonEmpty.head kernelPoints
+      initial =
+        map
+          (`subtractExactPoints` firstKernel)
+          (NonEmpty.toList sourcePoints)
+      halfPlanes =
+        [ strongestHalfPlane kernelPoints from to
+        | (from, to) <- cyclePairs sourcePoints
+        ]
+  clipped <- foldM clipPolygon initial halfPlanes
+  pure (ConvexPolygon . ExactLoop . rotateCycleLeast <$> convexHullPoints clipped)
+
+edgeDirections :: NonEmpty ExactPoint -> NonEmpty ExactVector
+edgeDirections = fmap (uncurry exactVectorFromPoints) . cyclePairsNonEmpty
+
+mergeDirections
+  :: NonEmpty ExactVector
+  -> NonEmpty ExactVector
+  -> NonEmpty ExactVector
+mergeDirections (left :| leftTail) (right :| rightTail) =
+  case compareExactVectorAngle left right of
+    LT -> left :| mergeRemaining leftTail (right : rightTail)
+    GT -> right :| mergeRemaining (left : leftTail) rightTail
+    EQ -> addExactVectors left right :| mergeRemaining leftTail rightTail
+
+mergeRemaining :: [ExactVector] -> [ExactVector] -> [ExactVector]
+mergeRemaining [] right = right
+mergeRemaining left [] = left
+mergeRemaining left@(leftHead : leftTail) right@(rightHead : rightTail) =
+  case compareExactVectorAngle leftHead rightHead of
+    LT -> leftHead : mergeRemaining leftTail right
+    GT -> rightHead : mergeRemaining left rightTail
+    EQ -> addExactVectors leftHead rightHead : mergeRemaining leftTail rightTail
+
+pointSweepKey :: ExactPoint -> (ExactRational, ExactRational)
+pointSweepKey point =
+  let (x, y) = exactPointCoordinates point
+   in (y, x)
+
+convexHullPoints :: [ExactPoint] -> Maybe (NonEmpty ExactPoint)
+convexHullPoints submitted =
+  case Set.toAscList (Set.fromList submitted) of
+    firstPoint : secondPoint : thirdPoint : remaining ->
+      let ordered = firstPoint : secondPoint : thirdPoint : remaining
+          lower = dropFinal (reverse (foldl' hullStep [] ordered))
+          upper = dropFinal (reverse (foldl' hullStep [] (reverse ordered)))
+       in case lower <> upper of
+            firstHullPoint : secondHullPoint : thirdHullPoint : hullTail ->
+              Just (firstHullPoint :| (secondHullPoint : thirdHullPoint : hullTail))
+            _ -> Nothing
+    _ -> Nothing
+
+hullStep :: [ExactPoint] -> ExactPoint -> [ExactPoint]
+hullStep (current : previous : remaining) candidate
+  | exactOrient2d previous current candidate /= GT =
+      hullStep (previous : remaining) candidate
+hullStep hull candidate = candidate : hull
+
+dropFinal :: [value] -> [value]
+dropFinal values =
+  case reverse values of
+    _ : remaining -> reverse remaining
+    [] -> []
+
+strongestHalfPlane
+  :: NonEmpty ExactPoint
+  -> ExactPoint
+  -> ExactPoint
+  -> (ExactPoint, ExactPoint)
+strongestHalfPlane kernelPoints from to =
+  let direction = exactVectorFromPoints from to
+      supportPoint =
+        case kernelPoints of
+          initial :| remaining ->
+            foldl'
+              (\selected candidate ->
+                 if directionPointCross direction candidate
+                      < directionPointCross direction selected
+                   then candidate
+                   else selected)
+              initial
+              remaining
+   in ( subtractExactPoints from supportPoint
+      , subtractExactPoints to supportPoint
+      )
+
+directionPointCross :: ExactVector -> ExactPoint -> ExactRational
+directionPointCross (ExactVector directionX directionY) point =
+  let (x, y) = exactPointCoordinates point
+   in directionX * y - directionY * x
+
+clipPolygon
+  :: [ExactPoint]
+  -> (ExactPoint, ExactPoint)
+  -> Either MinkowskiError [ExactPoint]
+clipPolygon [] _ = Right []
+clipPolygon polygon halfPlane =
+  concat <$> traverse (clipEdge halfPlane) (cyclePairsList polygon)
+
+clipEdge
+  :: (ExactPoint, ExactPoint)
+  -> (ExactPoint, ExactPoint)
+  -> Either MinkowskiError [ExactPoint]
+clipEdge (boundaryFrom, boundaryTo) (from, to) =
+  case (inside from, inside to) of
+    (True, True) -> Right [to]
+    (True, False) -> (: []) <$> supportingLineIntersection from to boundaryFrom boundaryTo
+    (False, True) -> do
+      crossing <- supportingLineIntersection from to boundaryFrom boundaryTo
+      pure [crossing, to]
+    (False, False) -> Right []
+ where
+  inside point = exactOrient2d boundaryFrom boundaryTo point /= LT
+
+supportingLineIntersection
+  :: ExactPoint
+  -> ExactPoint
+  -> ExactPoint
+  -> ExactPoint
+  -> Either MinkowskiError ExactPoint
+supportingLineIntersection lineFrom lineTo boundaryFrom boundaryTo = do
+  clippedSegment <- first MinkowskiInvalidSegment (exactSegment lineFrom lineTo)
+  boundarySegment <- first MinkowskiInvalidSegment (exactSegment boundaryFrom boundaryTo)
+  first MinkowskiLineIntersection
+    (exactSupportingLineIntersection clippedSegment boundarySegment)
+
+addExactPoints :: ExactPoint -> ExactPoint -> ExactPoint
+addExactPoints left right =
+  let (leftX, leftY) = exactPointCoordinates left
+      (rightX, rightY) = exactPointCoordinates right
+   in exactPoint (leftX + rightX) (leftY + rightY)
+
+subtractExactPoints :: ExactPoint -> ExactPoint -> ExactPoint
+subtractExactPoints left right =
+  let (leftX, leftY) = exactPointCoordinates left
+      (rightX, rightY) = exactPointCoordinates right
+   in exactPoint (leftX - rightX) (leftY - rightY)
+
+negateExactPoint :: ExactPoint -> ExactPoint
+negateExactPoint point =
+  let (x, y) = exactPointCoordinates point
+   in exactPoint (negate x) (negate y)
+
+cyclePairsList :: [value] -> [(value, value)]
+cyclePairsList = maybe [] cyclePairs . NonEmpty.nonEmpty
diff --git a/src-build/Moonlight/Triangulation/Internal/Minkowski/Types.hs b/src-build/Moonlight/Triangulation/Internal/Minkowski/Types.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Internal/Minkowski/Types.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | Closed vocabulary and invariant carriers for exact polygonal morphology.
+module Moonlight.Triangulation.Internal.Minkowski.Types
+  ( ConvexPolygon (..)
+  , StructuringElement (..)
+  , MinkowskiOperation (..)
+  , MinkowskiError (..)
+  , MinkowskiReceipt (..)
+  ) where
+
+import Control.DeepSeq (NFData)
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Exact
+  ( ExactGeometryError
+  , ExactIntersectionError
+  , ExactPoint
+  )
+import Moonlight.Triangulation.Handles.HandleDefs (FaceId)
+import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError)
+import Moonlight.Triangulation.Internal.Overlay.Types
+  ( OverlayCellId
+  , OverlayCellWitness
+  , OverlayError
+  )
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop
+  , RegionPublicationError
+  , RegionPointLocation
+  , RegionValidationError
+  )
+
+newtype ConvexPolygon = ConvexPolygon ExactLoop
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+newtype StructuringElement = StructuringElement ConvexPolygon
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data MinkowskiOperation
+  = MinkowskiAddition
+  | MinkowskiErosion
+  | MinkowskiOpening
+  | MinkowskiClosing
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data MinkowskiError
+  = MinkowskiInvalidConvexLoop !RegionValidationError
+  | MinkowskiInvalidSegment !ExactGeometryError
+  | MinkowskiNonConvexTurn !Int !Ordering
+  | MinkowskiOriginOutside !RegionPointLocation
+  | MinkowskiExactArithmetic !ExactArithmeticError
+  | MinkowskiLineIntersection !ExactIntersectionError
+  | MinkowskiOverlayFailed !(OverlayError Bool Bool)
+  | MinkowskiPublicationFailed !RegionPublicationError
+  | MinkowskiOverlayCellWitness !OverlayCellWitness
+  | MinkowskiFaceArity !FaceId !Int
+  | MinkowskiCandidateCellMissing !OverlayCellId
+  | MinkowskiInclusionAmbiguous !OverlayCellId !ExactPoint
+  | MinkowskiConvexHullDegenerate ![ExactPoint]
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+data MinkowskiReceipt = MinkowskiReceipt
+  { minkowskiOperation :: !MinkowskiOperation
+  , minkowskiInputComponents :: !Int
+  , minkowskiConvexPieces :: !Int
+  , minkowskiGeneratedPieces :: !Int
+  , minkowskiGeneratedConvolutionEdges :: !Int
+  , minkowskiOverlayPasses :: !Int
+  , minkowskiExactCrossings :: !Int
+  , minkowskiOutputCells :: !Int
+  , minkowskiExactCoordinateBitGrowth :: !Int
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
diff --git a/src-build/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs b/src-build/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs
@@ -0,0 +1,806 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | Exact source normalization and binary64 embedding certification. This
+-- module ends at the arrangement/resident seam: no DCEL face state crosses it.
+module Moonlight.Triangulation.Internal.Overlay.Arrangement
+  ( ExactEdgeKey
+  , AtomicEdge (..)
+  , atomicEdgeFrom
+  , atomicEdgeTo
+  , OverlayVertexSeed (..)
+  , ArrangementMetrics (..)
+  , CertifiedArrangement (..)
+  , certifyArrangement
+  , canonicalEdgeKey
+  , atomicKey
+  , compareAround
+  ) where
+
+import Data.Bifunctor (first)
+import Data.List (sortBy)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict (Map)
+import qualified Data.Set as Set
+import Data.Set (Set)
+import qualified Data.Vector as V
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Exact
+  ( ExactGeometryError
+  , ExactPoint
+  , ExactSegment
+  , SegmentRelation (SegmentsShareEndpoint)
+  , compareExactVectorAngle
+  , exactPointCoordinates
+  , exactPointFromPoint
+  , exactSegment
+  , exactSegmentEndpoints
+  , exactVectorFromPoints
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle
+  ( consecutivePairs
+  , orderedPair
+  , unorderedPairs
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational
+  , exactDivide
+  , exactRationalIsZero
+  )
+import Moonlight.Triangulation.Internal.ExactSegmentEvents
+  ( ExactSegmentEvent (..)
+  , ExactSegmentEventPlan
+  , ExactSweepSegmentId (..)
+  , exactSegmentEventPlan
+  , exactSegmentEvents
+  , exactSegmentPairChecks
+  , exactSegmentRelationMap
+  , exactSegmentSplitPoints
+  , exactSegmentSweepMaximumHeight
+  )
+import Moonlight.Triangulation.Internal.Overlay.Embedding
+  ( DraftIncidence (..)
+  , DraftNeighborhood (..)
+  , DraftReference (..)
+  , DraftSegmentId (..)
+  , DraftSourceId (..)
+  , DraftVertexId (..)
+  , ExactArrangementDraft (..)
+  , LocalEmbeddingCertificate (..)
+  , OverlayEmbeddingObstruction (..)
+  , certifyLocalEmbedding
+  )
+import Moonlight.Triangulation.Internal.Overlay.Types
+import Moonlight.Triangulation.Internal.Types (HasPosition (..), Point)
+import Moonlight.Triangulation.Region
+  ( ExactLoop
+  , PlanarLayer
+  , PolygonComponent
+  , exactLoopPoints
+  , planarLayerOutsideLabel
+  , planarLayerRegions
+  , planarRegionComponents
+  , polygonHoleLoops
+  , polygonOuterLoop
+  )
+
+type ExactEdgeKey = (ExactPoint, ExactPoint)
+
+data SourceBoundary leftLabel rightLabel
+  = LeftSourceBoundary
+      !ExactSegment
+      !leftLabel
+      !(BoundaryVertexRef 'LeftOverlayOperand)
+      !(BoundaryVertexRef 'LeftOverlayOperand)
+      !(BoundaryEdgeRef 'LeftOverlayOperand)
+  | RightSourceBoundary
+      !ExactSegment
+      !rightLabel
+      !(BoundaryVertexRef 'RightOverlayOperand)
+      !(BoundaryVertexRef 'RightOverlayOperand)
+      !(BoundaryEdgeRef 'RightOverlayOperand)
+
+type SourceBoundaryConstructor operand label leftLabel rightLabel =
+  ExactSegment
+  -> label
+  -> BoundaryVertexRef operand
+  -> BoundaryVertexRef operand
+  -> BoundaryEdgeRef operand
+  -> SourceBoundary leftLabel rightLabel
+
+data AtomicContribution leftLabel rightLabel =
+  AtomicContribution !(SourceBoundary leftLabel rightLabel) !Bool
+
+data AtomicEdge leftLabel rightLabel = AtomicEdge
+  { atomicEdgeSegment :: !ExactSegment
+  , atomicEdgeOrigin :: !OverlayEdgeOrigin
+  , atomicEdgeLeftTransition :: !(Maybe (leftLabel, leftLabel))
+  , atomicEdgeRightTransition :: !(Maybe (rightLabel, rightLabel))
+  }
+
+data OriginAccumulation = OriginAccumulation
+  { accumulatedLeftVertices :: !(Set (BoundaryVertexRef 'LeftOverlayOperand))
+  , accumulatedRightVertices :: !(Set (BoundaryVertexRef 'RightOverlayOperand))
+  , accumulatedLeftEdges :: !(Set (BoundaryEdgeRef 'LeftOverlayOperand))
+  , accumulatedRightEdges :: !(Set (BoundaryEdgeRef 'RightOverlayOperand))
+  }
+
+data OverlayVertexSeed = OverlayVertexSeed
+  { seedExactPoint :: !ExactPoint
+  , seedEmbeddedPoint :: !Point
+  , seedOrigin :: !OverlayVertexOrigin
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+
+instance HasPosition OverlayVertexSeed where
+  position = seedEmbeddedPoint
+
+data ArrangementMetrics = ArrangementMetrics
+  { arrangementInputSegments :: !Int
+  , arrangementRelationEvents :: !Int
+  , arrangementExactCrossings :: !Int
+  , arrangementOverlapIntervals :: !Int
+  , arrangementEmbeddingCandidates :: !Int
+  , arrangementTotalRelationChecks :: !Int
+  , arrangementSweepMaximumHeight :: !Int
+  }
+
+data CertifiedArrangement leftLabel rightLabel = CertifiedArrangement
+  { certifiedAtomicEdges :: !(V.Vector (AtomicEdge leftLabel rightLabel))
+  , certifiedVertexSeeds :: !(V.Vector OverlayVertexSeed)
+  , certifiedConstraints :: !(V.Vector (Int, Int))
+  , certifiedMetrics :: !ArrangementMetrics
+  }
+
+certifyArrangement
+  :: (Ord leftLabel, Ord rightLabel)
+  => PlanarLayer leftLabel
+  -> PlanarLayer rightLabel
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (CertifiedArrangement leftLabel rightLabel)
+certifyArrangement leftLayer rightLayer = do
+  sources <- flattenLayers leftLayer rightLayer
+  sourcePlan <-
+    first OverlaySegmentEventsInvalid
+      (exactSegmentEventPlan (V.map sourceExactSegment sources))
+  atomicEdges <- normalizeAtomicEdges leftLayer rightLayer sources sourcePlan
+  let atomicVector = V.fromList atomicEdges
+      pointIds = exactPointIds atomicVector
+      origins = vertexOrigins sources atomicVector
+      atomicRelations = atomicEndpointRelations atomicVector
+  draft <- exactArrangementDraft sources sourcePlan atomicVector pointIds atomicRelations
+  localCertificate <- first OverlayEmbeddingRefused (certifyLocalEmbedding draft)
+  projectedPoints <- projectedExactPoints localCertificate
+  projectedSegments <- projectedAtomicSegments atomicVector pointIds projectedPoints
+  projectedPlan <-
+    first OverlaySegmentEventsInvalid
+      (exactSegmentEventPlan projectedSegments)
+  dischargeGlobalRelations atomicRelations projectedPlan
+  seeds <- overlayVertexSeeds pointIds origins localCertificate
+  constraints <- atomicConstraints pointIds atomicVector
+  let events = exactSegmentEvents sourcePlan
+  pure
+    CertifiedArrangement
+      { certifiedAtomicEdges = atomicVector
+      , certifiedVertexSeeds = seeds
+      , certifiedConstraints = constraints
+      , certifiedMetrics =
+          ArrangementMetrics
+            { arrangementInputSegments = V.length sources
+            , arrangementRelationEvents = length events
+            , arrangementExactCrossings =
+                length [() | ExactProperCrossing {} <- events]
+            , arrangementOverlapIntervals =
+                length [() | ExactCollinearOverlap {} <- events]
+            , arrangementEmbeddingCandidates = Map.size pointIds
+            , arrangementTotalRelationChecks =
+                exactSegmentPairChecks sourcePlan
+                  + exactSegmentPairChecks projectedPlan
+            , arrangementSweepMaximumHeight =
+                max
+                  (exactSegmentSweepMaximumHeight sourcePlan)
+                  (exactSegmentSweepMaximumHeight projectedPlan)
+            }
+      }
+flattenLayers
+  :: PlanarLayer leftLabel
+  -> PlanarLayer rightLabel
+  -> Either (OverlayError leftLabel rightLabel) (V.Vector (SourceBoundary leftLabel rightLabel))
+flattenLayers leftLayer rightLayer = do
+  leftSources <- flattenLayer LeftSourceBoundary leftLayer
+  rightSources <- flattenLayer RightSourceBoundary rightLayer
+  pure (V.fromList (leftSources <> rightSources))
+
+flattenLayer
+  :: SourceBoundaryConstructor operand label leftLabel rightLabel
+  -> PlanarLayer label
+  -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]
+flattenLayer makeBoundary layer =
+  fmap concat
+    ( traverse
+        (\(componentIndex, (label, component)) ->
+           flattenComponent makeBoundary componentIndex label component)
+        (zip [0 ..] (labelledComponents layer))
+    )
+{-# INLINE flattenLayer #-}
+
+labelledComponents :: PlanarLayer label -> [(label, PolygonComponent)]
+labelledComponents layer =
+  [ (label, component)
+  | (label, region) <- Map.toAscList (planarLayerRegions layer)
+  , component <- planarRegionComponents region
+  ]
+
+flattenComponent
+  :: SourceBoundaryConstructor operand label leftLabel rightLabel
+  -> Int
+  -> label
+  -> PolygonComponent
+  -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]
+flattenComponent makeBoundary componentIndex label component = do
+  outer <-
+    flattenLoop
+      makeBoundary
+      componentIndex
+      BoundaryOuterLoop
+      label
+      (polygonOuterLoop component)
+  holes <-
+    fmap concat
+      ( traverse
+          (\(holeIndex, loop) ->
+             flattenLoop
+               makeBoundary
+               componentIndex
+               (BoundaryHoleLoop holeIndex)
+               label
+               loop)
+          (zip [0 ..] (polygonHoleLoops component))
+      )
+  pure (outer <> holes)
+{-# INLINE flattenComponent #-}
+
+flattenLoop
+  :: SourceBoundaryConstructor operand label leftLabel rightLabel
+  -> Int
+  -> BoundaryLoopRef
+  -> label
+  -> ExactLoop
+  -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]
+flattenLoop makeBoundary componentIndex loopRef label loop =
+  traverse constructSource (indexedCycle (exactLoopPoints loop))
+ where
+  constructSource (edgeIndex, fromIndex, from, toIndex, to) = do
+    segment <- first (sourceGeometryError from) (exactSegment from to)
+    pure
+      ( makeBoundary
+          segment
+          label
+          (BoundaryVertexRef componentIndex loopRef fromIndex)
+          (BoundaryVertexRef componentIndex loopRef toIndex)
+          (BoundaryEdgeRef componentIndex loopRef edgeIndex)
+      )
+{-# INLINE flattenLoop #-}
+
+sourceGeometryError
+  :: ExactPoint
+  -> ExactGeometryError
+  -> OverlayError leftLabel rightLabel
+sourceGeometryError point _ = OverlayArrangementInvalid (OverlayRotationDegenerate point)
+
+indexedCycle :: NonEmpty value -> [(Int, Int, value, Int, value)]
+indexedCycle (firstValue :| remaining) =
+  let values = firstValue : remaining
+      count = length values
+   in [ (index, index, from, (index + 1) `mod` count, to)
+      | (index, (from, to)) <- zip [0 ..] (zip values (remaining <> [firstValue]))
+      ]
+
+sourceExactSegment :: SourceBoundary leftLabel rightLabel -> ExactSegment
+sourceExactSegment (LeftSourceBoundary segment _ _ _ _) = segment
+sourceExactSegment (RightSourceBoundary segment _ _ _ _) = segment
+
+normalizeAtomicEdges
+  :: (Ord leftLabel, Ord rightLabel)
+  => PlanarLayer leftLabel
+  -> PlanarLayer rightLabel
+  -> V.Vector (SourceBoundary leftLabel rightLabel)
+  -> ExactSegmentEventPlan
+  -> Either (OverlayError leftLabel rightLabel) [AtomicEdge leftLabel rightLabel]
+normalizeAtomicEdges leftLayer rightLayer sources plan =
+  fmap concat
+    ( traverse
+        (resolveAtomicContributions leftOutside rightOutside)
+        (Map.toAscList grouped)
+    )
+ where
+  leftOutside = planarLayerOutsideLabel leftLayer
+  rightOutside = planarLayerOutsideLabel rightLayer
+  grouped =
+    V.ifoldl'
+      (\groups sourceIndex source ->
+         foldl'
+           (insertAtomic source)
+           groups
+           (consecutivePairs (exactSegmentSplitPoints plan (ExactSweepSegmentId sourceIndex))))
+      Map.empty
+      sources
+  insertAtomic
+    :: SourceBoundary leftLabel' rightLabel'
+    -> Map ExactEdgeKey [AtomicContribution leftLabel' rightLabel']
+    -> (ExactPoint, ExactPoint)
+    -> Map ExactEdgeKey [AtomicContribution leftLabel' rightLabel']
+  insertAtomic source groups (from, to)
+    | from == to = groups
+    | otherwise =
+        let key@(canonicalFrom, _) = canonicalEdgeKey from to
+            contribution = AtomicContribution source (from == canonicalFrom)
+         in Map.insertWith (<>) key [contribution] groups
+
+resolveAtomicContributions
+  :: (Ord leftLabel, Ord rightLabel)
+  => leftLabel
+  -> rightLabel
+  -> (ExactEdgeKey, [AtomicContribution leftLabel rightLabel])
+  -> Either (OverlayError leftLabel rightLabel) [AtomicEdge leftLabel rightLabel]
+resolveAtomicContributions leftOutside rightOutside ((from, to), contributions) = do
+  let
+      ( leftLabelsOnLeft
+        , leftLabelsOnRight
+        , rightLabelsOnLeft
+        , rightLabelsOnRight
+        , leftSources
+        , rightSources
+        ) =
+          foldl'
+            collectContribution
+            (Set.empty, Set.empty, Set.empty, Set.empty, Set.empty, Set.empty)
+            contributions
+      origin =
+        OverlayEdgeOrigin
+          (Set.toAscList leftSources)
+          (Set.toAscList rightSources)
+  leftTransition <-
+    resolveTransition
+      OverlayLeftSourceSideConflict
+      origin
+      leftOutside
+      leftLabelsOnLeft
+      leftLabelsOnRight
+  rightTransition <-
+    resolveTransition
+      OverlayRightSourceSideConflict
+      origin
+      rightOutside
+      rightLabelsOnLeft
+      rightLabelsOnRight
+  if transitionIsIdentity leftTransition && transitionIsIdentity rightTransition
+    then Right []
+    else do
+      segment <- first (sourceGeometryError from) (exactSegment from to)
+      Right
+        [ AtomicEdge
+            { atomicEdgeSegment = segment
+            , atomicEdgeOrigin = origin
+            , atomicEdgeLeftTransition = leftTransition
+            , atomicEdgeRightTransition = rightTransition
+            }
+        ]
+ where
+  collectContribution
+    :: (Ord leftLabel', Ord rightLabel')
+    => ( Set leftLabel'
+       , Set leftLabel'
+       , Set rightLabel'
+       , Set rightLabel'
+       , Set (BoundaryEdgeRef 'LeftOverlayOperand)
+       , Set (BoundaryEdgeRef 'RightOverlayOperand)
+       )
+    -> AtomicContribution leftLabel' rightLabel'
+    -> ( Set leftLabel'
+       , Set leftLabel'
+       , Set rightLabel'
+       , Set rightLabel'
+       , Set (BoundaryEdgeRef 'LeftOverlayOperand)
+       , Set (BoundaryEdgeRef 'RightOverlayOperand)
+       )
+  collectContribution
+    ( !leftLeft
+      , !leftRight
+      , !rightLeft
+      , !rightRight
+      , !leftSources
+      , !rightSources
+      )
+    contribution =
+    case contribution of
+      AtomicContribution (LeftSourceBoundary _ label _ _ reference) follows ->
+        ( if follows then Set.insert label leftLeft else leftLeft
+        , if follows then leftRight else Set.insert label leftRight
+        , rightLeft
+        , rightRight
+        , Set.insert reference leftSources
+        , rightSources
+        )
+      AtomicContribution (RightSourceBoundary _ label _ _ reference) follows ->
+        ( leftLeft
+        , leftRight
+        , if follows then Set.insert label rightLeft else rightLeft
+        , if follows then rightRight else Set.insert label rightRight
+        , leftSources
+        , Set.insert reference rightSources
+        )
+
+resolveTransition
+  :: (OverlayEdgeOrigin -> NonEmpty label -> OverlayArrangementObstruction leftLabel rightLabel)
+  -> OverlayEdgeOrigin
+  -> label
+  -> Set label
+  -> Set label
+  -> Either (OverlayError leftLabel rightLabel) (Maybe (label, label))
+resolveTransition sideConflict origin outside labelsOnLeft labelsOnRight
+  | Set.null labelsOnLeft && Set.null labelsOnRight = Right Nothing
+  | otherwise =
+      Just
+        <$> ((,)
+               <$> resolveSide sideConflict origin outside labelsOnLeft
+               <*> resolveSide sideConflict origin outside labelsOnRight)
+{-# INLINE resolveTransition #-}
+
+transitionIsIdentity :: Eq label => Maybe (label, label) -> Bool
+transitionIsIdentity Nothing = True
+transitionIsIdentity (Just (leftLabel, rightLabel)) = leftLabel == rightLabel
+
+resolveSide
+  :: (OverlayEdgeOrigin -> NonEmpty label -> OverlayArrangementObstruction leftLabel rightLabel)
+  -> OverlayEdgeOrigin
+  -> label
+  -> Set label
+  -> Either (OverlayError leftLabel rightLabel) label
+resolveSide sideConflict origin outside labels =
+  case Set.toAscList labels of
+    [] -> Right outside
+    [label] -> Right label
+    firstLabel : remaining ->
+      Left
+        ( OverlayArrangementInvalid
+            (sideConflict origin (firstLabel :| remaining))
+        )
+{-# INLINE resolveSide #-}
+
+exactPointIds
+  :: V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Map ExactPoint DraftVertexId
+exactPointIds edges =
+  Map.fromAscList
+    ( zip
+        (Set.toAscList (V.foldl' collect Set.empty edges))
+      (map DraftVertexId [0 ..])
+    )
+ where
+  collect
+    :: Set ExactPoint
+    -> AtomicEdge leftLabel rightLabel
+    -> Set ExactPoint
+  collect points edge = Set.insert (atomicEdgeFrom edge) (Set.insert (atomicEdgeTo edge) points)
+
+vertexOrigins
+  :: V.Vector (SourceBoundary leftLabel rightLabel)
+  -> V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Map ExactPoint OverlayVertexOrigin
+vertexOrigins sources atomicEdges =
+  Map.map finalizeOrigin
+    ( V.foldl'
+        addAtomicOrigin
+        (V.foldl' addSourceEndpoints Map.empty sources)
+        atomicEdges
+    )
+
+emptyOrigin :: OriginAccumulation
+emptyOrigin = OriginAccumulation Set.empty Set.empty Set.empty Set.empty
+
+mergeOrigin :: OriginAccumulation -> OriginAccumulation -> OriginAccumulation
+mergeOrigin left right =
+  OriginAccumulation
+    { accumulatedLeftVertices = accumulatedLeftVertices left <> accumulatedLeftVertices right
+    , accumulatedRightVertices = accumulatedRightVertices left <> accumulatedRightVertices right
+    , accumulatedLeftEdges = accumulatedLeftEdges left <> accumulatedLeftEdges right
+    , accumulatedRightEdges = accumulatedRightEdges left <> accumulatedRightEdges right
+    }
+
+addSourceEndpoints
+  :: Map ExactPoint OriginAccumulation
+  -> SourceBoundary leftLabel rightLabel
+  -> Map ExactPoint OriginAccumulation
+addSourceEndpoints origins source =
+  case source of
+    LeftSourceBoundary segment _ fromReference toReference _ ->
+      let (from, to) = exactSegmentEndpoints segment
+       in insertOrigin to (emptyOrigin{accumulatedLeftVertices = Set.singleton toReference})
+            (insertOrigin from (emptyOrigin{accumulatedLeftVertices = Set.singleton fromReference}) origins)
+    RightSourceBoundary segment _ fromReference toReference _ ->
+      let (from, to) = exactSegmentEndpoints segment
+       in insertOrigin to (emptyOrigin{accumulatedRightVertices = Set.singleton toReference})
+            (insertOrigin from (emptyOrigin{accumulatedRightVertices = Set.singleton fromReference}) origins)
+
+addAtomicOrigin
+  :: Map ExactPoint OriginAccumulation
+  -> AtomicEdge leftLabel rightLabel
+  -> Map ExactPoint OriginAccumulation
+addAtomicOrigin origins edge =
+  let origin = atomicEdgeOrigin edge
+      accumulation =
+        emptyOrigin
+          { accumulatedLeftEdges = Set.fromList (overlayEdgeLeftSources origin)
+          , accumulatedRightEdges = Set.fromList (overlayEdgeRightSources origin)
+          }
+   in insertOrigin (atomicEdgeTo edge) accumulation
+        (insertOrigin (atomicEdgeFrom edge) accumulation origins)
+
+insertOrigin
+  :: ExactPoint
+  -> OriginAccumulation
+  -> Map ExactPoint OriginAccumulation
+  -> Map ExactPoint OriginAccumulation
+insertOrigin = Map.insertWith mergeOrigin
+
+finalizeOrigin :: OriginAccumulation -> OverlayVertexOrigin
+finalizeOrigin accumulated =
+  OverlayVertexOrigin
+    { overlayOriginLeftVertices = Set.toAscList (accumulatedLeftVertices accumulated)
+    , overlayOriginRightVertices = Set.toAscList (accumulatedRightVertices accumulated)
+    , overlayOriginLeftEdges = Set.toAscList (accumulatedLeftEdges accumulated)
+    , overlayOriginRightEdges = Set.toAscList (accumulatedRightEdges accumulated)
+    }
+
+exactArrangementDraft
+  :: V.Vector (SourceBoundary leftLabel rightLabel)
+  -> ExactSegmentEventPlan
+  -> V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Map ExactPoint DraftVertexId
+  -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation
+  -> Either (OverlayError leftLabel rightLabel) ExactArrangementDraft
+exactArrangementDraft sources sourcePlan atomicEdges pointIds atomicRelations = do
+  draftSegmentsMap <-
+    Map.fromList
+      <$> traverse
+        (\(segmentIndex, edge) -> do
+           from <- requireDraftVertex pointIds (atomicEdgeFrom edge)
+           to <- requireDraftVertex pointIds (atomicEdgeTo edge)
+           pure (DraftSegmentId segmentIndex, (from, to)))
+        (V.toList (V.indexed atomicEdges))
+  memberships <-
+    Map.fromList
+      <$> traverse
+        (\(sourceIndex, source) -> do
+           values <-
+             traverse
+               (\point -> do
+                  parameter <- exactSourceParameter source point
+                  vertex <- requireDraftVertex pointIds point
+                  pure (parameter, vertex))
+               (exactSegmentSplitPoints sourcePlan (ExactSweepSegmentId sourceIndex))
+           pure (DraftSourceId sourceIndex, values))
+        (V.toList (V.indexed sources))
+  neighborhoods <- buildDraftNeighborhoods atomicEdges pointIds
+  let incidences =
+        [ DraftIncidence
+            (DraftSegmentId leftIndex)
+            (DraftSegmentId rightIndex)
+            relation
+        | ((ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex), relation) <-
+            Map.toAscList atomicRelations
+        ]
+  pure
+    ExactArrangementDraft
+      { draftVertices = Map.fromList [(vertexId, point) | (point, vertexId) <- Map.toAscList pointIds]
+      , draftSegments = draftSegmentsMap
+      , draftSourceMemberships = memberships
+      , draftIncidences = incidences
+      , draftNeighborhoods = neighborhoods
+      }
+
+requireDraftVertex
+  :: Map ExactPoint DraftVertexId
+  -> ExactPoint
+  -> Either (OverlayError leftLabel rightLabel) DraftVertexId
+requireDraftVertex pointIds point =
+  case Map.lookup point pointIds of
+    Just vertex -> Right vertex
+    Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))
+
+exactSourceParameter
+  :: SourceBoundary leftLabel rightLabel
+  -> ExactPoint
+  -> Either (OverlayError leftLabel rightLabel) ExactRational
+exactSourceParameter source point =
+  let (from, to) = exactSegmentEndpoints (sourceExactSegment source)
+      (fromX, fromY) = exactPointCoordinates from
+      (toX, toY) = exactPointCoordinates to
+      (pointX, pointY) = exactPointCoordinates point
+      (numerator, denominator) =
+        if exactRationalIsZero (toX - fromX)
+          then (pointY - fromY, toY - fromY)
+          else (pointX - fromX, toX - fromX)
+   in first OverlayExactArithmetic (exactDivide numerator denominator)
+
+buildDraftNeighborhoods
+  :: V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Map ExactPoint DraftVertexId
+  -> Either (OverlayError leftLabel rightLabel) [DraftNeighborhood]
+buildDraftNeighborhoods edges pointIds =
+  traverse neighborhood (Map.toAscList pointIds)
+ where
+  neighborhood (centerPoint, centerId) = do
+    let neighbors = Map.findWithDefault Set.empty centerPoint adjacency
+    orderedPoints <-
+      case NonEmpty.nonEmpty (sortBy (compareAround centerPoint) (Set.toList neighbors)) of
+        Just points -> Right points
+        Nothing -> Left (OverlayArrangementInvalid (OverlayRotationDegenerate centerPoint))
+    orderedIds <- traverse (requireDraftVertex pointIds) orderedPoints
+    pure (DraftNeighborhood centerId orderedIds)
+  adjacency =
+    V.foldl'
+      (\graph edge ->
+         Map.insertWith Set.union (atomicEdgeTo edge) (Set.singleton (atomicEdgeFrom edge))
+           (Map.insertWith Set.union (atomicEdgeFrom edge) (Set.singleton (atomicEdgeTo edge)) graph))
+      Map.empty
+      edges
+
+compareAround :: ExactPoint -> ExactPoint -> ExactPoint -> Ordering
+compareAround center left right =
+  case
+    compareExactVectorAngle
+      (exactVectorFromPoints center left)
+      (exactVectorFromPoints center right) of
+    EQ -> compare left right
+    ordering -> ordering
+
+-- | Normalization splits every source at every exact event and coalesces
+-- duplicate intervals. Distinct atomics can therefore meet only at a stored
+-- endpoint; their entire relation section is the endpoint-incidence index.
+atomicEndpointRelations
+  :: V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation
+atomicEndpointRelations edges =
+  Map.fromList
+    [ ( orderedPair
+          (ExactSweepSegmentId leftIndex)
+          (ExactSweepSegmentId rightIndex)
+      , SegmentsShareEndpoint
+      )
+    | incident <- Map.elems incidenceByPoint
+    , (leftIndex, rightIndex) <- unorderedPairs (Set.toAscList incident)
+    ]
+ where
+  incidenceByPoint =
+    V.ifoldl'
+      (\incidence index edge ->
+         Map.insertWith Set.union (atomicEdgeTo edge) (Set.singleton index)
+           ( Map.insertWith Set.union
+               (atomicEdgeFrom edge)
+               (Set.singleton index)
+               incidence
+           ))
+      Map.empty
+      edges
+projectedExactPoints
+  :: LocalEmbeddingCertificate
+  -> Either (OverlayError leftLabel rightLabel) (Map DraftVertexId ExactPoint)
+projectedExactPoints certificate =
+  Map.traverseWithKey
+    (\vertex point ->
+       first
+         (\projectionError ->
+            OverlayEmbeddingRefused
+              (VertexProjectionRefused vertex projectionError :| []))
+         (exactPointFromPoint point))
+    (certificateRoundedVertices certificate)
+
+projectedAtomicSegments
+  :: V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Map ExactPoint DraftVertexId
+  -> Map DraftVertexId ExactPoint
+  -> Either (OverlayError leftLabel rightLabel) (V.Vector ExactSegment)
+projectedAtomicSegments edges pointIds projected =
+  V.imapM project edges
+ where
+  project segmentIndex edge = do
+    fromId <- requireDraftVertex pointIds (atomicEdgeFrom edge)
+    toId <- requireDraftVertex pointIds (atomicEdgeTo edge)
+    from <- requireProjected fromId
+    to <- requireProjected toId
+    first
+      (\_ ->
+         OverlayEmbeddingRefused
+           ( ProjectedSegmentCollapsed
+               (DraftSegmentId segmentIndex)
+               fromId
+               toId
+               :| []
+           ))
+      (exactSegment from to)
+  requireProjected vertex =
+    case Map.lookup vertex projected of
+      Just point -> Right point
+      Nothing ->
+        Left
+          ( OverlayEmbeddingRefused
+              (DraftReferenceMissing (DraftVertexReference vertex) :| [])
+          )
+
+dischargeGlobalRelations
+  :: Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation
+  -> ExactSegmentEventPlan
+  -> Either (OverlayError leftLabel rightLabel) ()
+dischargeGlobalRelations exactRelations projectedPlan =
+  case NonEmpty.nonEmpty obstructions of
+    Nothing -> Right ()
+    Just failures -> Left (OverlayEmbeddingRefused failures)
+ where
+  projectedRelations = exactSegmentRelationMap projectedPlan
+  keys = Set.toAscList (Map.keysSet exactRelations <> Map.keysSet projectedRelations)
+  obstructions = concatMap compareRelation keys
+  compareRelation key@(ExactSweepSegmentId leftId, ExactSweepSegmentId rightId) =
+    case (Map.lookup key exactRelations, Map.lookup key projectedRelations) of
+      (Nothing, Just projected) ->
+        [GlobalRelationAdded (DraftSegmentId leftId) (DraftSegmentId rightId) projected]
+      (Just exact, Nothing) ->
+        [GlobalRelationRemoved (DraftSegmentId leftId) (DraftSegmentId rightId) exact]
+      (Just exact, Just projected)
+        | exact /= projected ->
+            [GlobalRelationChanged (DraftSegmentId leftId) (DraftSegmentId rightId) exact projected]
+      _ -> []
+
+overlayVertexSeeds
+  :: Map ExactPoint DraftVertexId
+  -> Map ExactPoint OverlayVertexOrigin
+  -> LocalEmbeddingCertificate
+  -> Either (OverlayError leftLabel rightLabel) (V.Vector OverlayVertexSeed)
+overlayVertexSeeds pointIds origins certificate =
+  V.fromList
+    <$> traverse
+      (\(point, vertexId) -> do
+         embedded <-
+           case Map.lookup vertexId (certificateRoundedVertices certificate) of
+             Just value -> Right value
+             Nothing ->
+               Left
+                 ( OverlayEmbeddingRefused
+                     (DraftReferenceMissing (DraftVertexReference vertexId) :| [])
+                 )
+         origin <-
+           case Map.lookup point origins of
+             Just value -> Right value
+             Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))
+         pure (OverlayVertexSeed point embedded origin))
+      (Map.toAscList pointIds)
+
+atomicConstraints
+  :: Map ExactPoint DraftVertexId
+  -> V.Vector (AtomicEdge leftLabel rightLabel)
+  -> Either (OverlayError leftLabel rightLabel) (V.Vector (Int, Int))
+atomicConstraints pointIds =
+  V.mapM
+    (\edge -> do
+       DraftVertexId from <- requireDraftVertex pointIds (atomicEdgeFrom edge)
+       DraftVertexId to <- requireDraftVertex pointIds (atomicEdgeTo edge)
+       pure (from, to))
+
+
+canonicalEdgeKey :: ExactPoint -> ExactPoint -> ExactEdgeKey
+canonicalEdgeKey = orderedPair
+
+atomicEdgeFrom :: AtomicEdge leftLabel rightLabel -> ExactPoint
+atomicEdgeFrom = fst . exactSegmentEndpoints . atomicEdgeSegment
+
+atomicEdgeTo :: AtomicEdge leftLabel rightLabel -> ExactPoint
+atomicEdgeTo = snd . exactSegmentEndpoints . atomicEdgeSegment
+
+atomicKey :: AtomicEdge leftLabel rightLabel -> ExactEdgeKey
+atomicKey edge = (atomicEdgeFrom edge, atomicEdgeTo edge)
diff --git a/src-build/Moonlight/Triangulation/Internal/Overlay/Resident.hs b/src-build/Moonlight/Triangulation/Internal/Overlay/Resident.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Internal/Overlay/Resident.hs
@@ -0,0 +1,868 @@
+{-# LANGUAGE DataKinds #-}
+
+-- | Resident DCEL descent and exact-cell gluing. The certified arrangement is
+-- authoritative at this seam; diagonal schedules may change only its resident
+-- triangulation, never its exact cell descriptors.
+module Moonlight.Triangulation.Internal.Overlay.Resident
+  ( OverlayDiagonalSchedule (..)
+  , residentOverlay
+  , faceLabels
+  , regionFaceLabels
+  , vertexSupport
+  , edgeSupport
+  ) where
+
+import Control.Monad (foldM)
+import Data.Bifunctor (first)
+import Data.Foldable (traverse_)
+import Data.List (partition, sort, sortBy)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict (Map)
+import Data.Maybe (listToMaybe, mapMaybe)
+import qualified Data.Sequence as Seq
+import qualified Data.Set as Set
+import Data.Set (Set)
+import qualified Data.Vector as V
+import qualified Moonlight.Triangulation.Dcel as Dcel
+import Moonlight.Triangulation.Dcel
+  ( faceData
+  , faceDirectedEdges
+  , imapUndirectedEdges
+  , incidentFace
+  , isConstraintEdge
+  , numInnerFaces
+  , numVertices
+  , outerFace
+  , undirectedEndpoints
+  , vertexData
+  , vertexOutgoingEdges
+  )
+import Moonlight.Triangulation.Exact (ExactPoint, exactOrient2d)
+import Moonlight.Triangulation.FloodFillIterator
+  ( FaceComponent
+  , componentBoundary
+  , faceComponentFaces
+  , faceComponents
+  )
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( DirectedEdgeId
+  , FaceId
+  , UndirectedEdgeId
+  , VertexId
+  , asUndirected
+  , directedPair
+  , reverseEdge
+  )
+import Moonlight.Triangulation.Handles.Iterators.FixedIterators
+  ( allFaces
+  , innerFaces
+  , undirectedEdges
+  )
+import Moonlight.Triangulation.Internal.BoxedPaged (boxedFromVector)
+import Moonlight.Triangulation.Internal.BoundaryCycle (consecutivePairs)
+import Moonlight.Triangulation.Internal.Canonical (canonicalize)
+import Moonlight.Triangulation.Internal.Cdt.Build (constrainedDelaunay)
+import Moonlight.Triangulation.Internal.Cdt.Types (CdtError (..))
+import Moonlight.Triangulation.Internal.Overlay.Arrangement
+  ( ArrangementMetrics (..)
+  , AtomicEdge (..)
+  , atomicEdgeFrom
+  , atomicEdgeTo
+  , CertifiedArrangement (..)
+  , ExactEdgeKey
+  , OverlayVertexSeed (..)
+  , atomicKey
+  , canonicalEdgeKey
+  , compareAround
+  )
+import Moonlight.Triangulation.Internal.Overlay.Types
+import Moonlight.Triangulation.Internal.Region.Publication
+  ( polygonComponentFromBoundaryCoordinates
+  )
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop
+  , PolygonComponent
+  , RegionPublicationError (..)
+  )
+import Moonlight.Triangulation.Internal.Representation
+  ( BuildResult (..)
+  , Triangulation (..)
+  )
+import Moonlight.Triangulation.Internal.Types
+  ( ConstraintMode (Constrained)
+  , ElementDefaults (..)
+  )
+import Moonlight.Triangulation.Region (exactLoop)
+
+data OverlayDiagonalSchedule
+  = CanonicalOverlayDiagonals
+  | FlipFirstAdmissibleDiagonal
+  deriving stock (Eq, Ord, Show)
+
+data ComponentDraft leftLabel rightLabel = ComponentDraft
+  { componentDraftLabels :: !(leftLabel, rightLabel)
+  , componentDraftFaces :: !FaceComponent
+  , componentDraftPolygon :: !PolygonComponent
+  , componentDraftTouchesOuter :: !Bool
+  }
+
+residentOverlay
+  :: (Ord leftLabel, Ord rightLabel)
+  => OverlayDiagonalSchedule
+  -> (leftLabel, rightLabel)
+  -> CertifiedArrangement leftLabel rightLabel
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (OverlayResult leftLabel rightLabel)
+residentOverlay diagonalSchedule outsidePair certified = do
+  let atomicVector = certifiedAtomicEdges certified
+      seeds = certifiedVertexSeeds certified
+      constraints = certifiedConstraints certified
+      metrics = certifiedMetrics certified
+      defaults = ElementDefaults () () ()
+  built <- first OverlayBuildFailed (constrainedDelaunay defaults seeds constraints)
+  let resident = buildTriangulation built
+  if numVertices resident == V.length seeds
+    then Right ()
+    else
+      Left
+        ( OverlayProvenanceIncomplete
+            (OverlayEmbeddedVertexCountMismatch (V.length seeds) (numVertices resident))
+        )
+  canonicalResident <-
+    first (OverlayBuildFailed . CdtBuildError) (canonicalize resident)
+  let atomicByKey =
+        Map.fromList
+          [ (atomicKey edge, edge)
+          | edge <- V.toList atomicVector
+          ]
+  validateAtomicConstraints canonicalResident atomicByKey
+  scheduledResident <-
+    applyDiagonalSchedule
+      diagonalSchedule
+      defaults
+      seeds
+      constraints
+      canonicalResident
+  labelledFaces <- labelResidentFaces outsidePair scheduledResident atomicByKey
+  componentDrafts <- residentComponentDrafts scheduledResident labelledFaces
+  let (unboundedDrafts, boundedDrafts) =
+        partitionDrafts outsidePair componentDrafts
+  numberedBounded <- numberBoundedComponents boundedDrafts
+  let cellIdByFace =
+        Map.fromList
+          ( [ (face, OverlayCellId 0)
+            | draftComponent <- unboundedDrafts
+            , face <- faceComponentFaces (componentDraftFaces draftComponent)
+            ]
+              <> [ (face, cellId)
+                 | (cellId, draftComponent) <- numberedBounded
+                 , face <- faceComponentFaces (componentDraftFaces draftComponent)
+                 ]
+          )
+  unboundedLoops <-
+    unboundedCellBoundaryLoops scheduledResident atomicByKey cellIdByFace
+  let cells =
+        V.fromList
+          ( OverlayCell
+              { overlayCellLeft = fst outsidePair
+              , overlayCellRight = snd outsidePair
+              , overlayCellGeometry = UnboundedOverlayCell unboundedLoops
+              }
+              : [ OverlayCell
+                    { overlayCellLeft = fst (componentDraftLabels draftComponent)
+                    , overlayCellRight = snd (componentDraftLabels draftComponent)
+                    , overlayCellGeometry =
+                        BoundedOverlayCell (componentDraftPolygon draftComponent)
+                    }
+                | (_, draftComponent) <- numberedBounded
+                ]
+          )
+  withFaces <- attachFaceCells cellIdByFace scheduledResident
+  let withVertices = attachOverlayVertices withFaces
+      withEdges =
+        imapUndirectedEdges
+          OverlayDiagonal
+          (\edge _ ->
+             case Map.lookup (residentEdgeKey withVertices edge) atomicByKey of
+               Nothing -> OverlayDiagonal
+               Just atomic -> OverlayBoundary (atomicEdgeOrigin atomic))
+          withVertices
+      receipt =
+        OverlayReceipt
+          { overlayInputSegments = arrangementInputSegments metrics
+          , overlayRelationEvents = arrangementRelationEvents metrics
+          , overlayExactCrossings = arrangementExactCrossings metrics
+          , overlayOverlapIntervals = arrangementOverlapIntervals metrics
+          , overlayAtomicEdges = V.length atomicVector
+          , overlayOutputVertices = numVertices withEdges
+          , overlayArrangementCells = V.length cells
+          , overlayResidentFaces = numInnerFaces withEdges
+          , overlayEmbeddingCandidates = arrangementEmbeddingCandidates metrics
+          , overlayTotalRelationChecks = arrangementTotalRelationChecks metrics
+          , overlaySweepMaximumHeight = arrangementSweepMaximumHeight metrics
+          }
+  pure
+    OverlayResult
+      { overlayResultTriangulation = withEdges
+      , overlayResultCells = cells
+      , overlayResultOutsideLabels = outsidePair
+      , overlayResultReceipt = receipt
+      }
+
+applyDiagonalSchedule
+  :: OverlayDiagonalSchedule
+  -> ElementDefaults () () ()
+  -> V.Vector OverlayVertexSeed
+  -> V.Vector (Int, Int)
+  -> Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (Triangulation 'Constrained OverlayVertexSeed () () ())
+applyDiagonalSchedule CanonicalOverlayDiagonals _ _ _ triangulation =
+  Right triangulation
+applyDiagonalSchedule
+  FlipFirstAdmissibleDiagonal
+  defaults
+  seeds
+  constraints
+  triangulation =
+    case firstAlternativeDiagonal triangulation of
+      Nothing -> Right triangulation
+      Just (from, to) -> do
+        let indexByPoint =
+              Map.fromList
+                [ (seedExactPoint seed, index)
+                | (index, seed) <- V.toList (V.indexed seeds)
+                ]
+        fromIndex <- requireSeedIndex indexByPoint from
+        toIndex <- requireSeedIndex indexByPoint to
+        rebuilt <-
+          first OverlayBuildFailed
+            (constrainedDelaunay defaults seeds (V.snoc constraints (fromIndex, toIndex)))
+        first (OverlayBuildFailed . CdtBuildError)
+          (canonicalize (buildTriangulation rebuilt))
+
+requireSeedIndex
+  :: Map ExactPoint Int
+  -> ExactPoint
+  -> Either (OverlayError leftLabel rightLabel) Int
+requireSeedIndex indexByPoint point =
+  case Map.lookup point indexByPoint of
+    Just index -> Right index
+    Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))
+
+firstAlternativeDiagonal
+  :: Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Maybe ExactEdgeKey
+firstAlternativeDiagonal triangulation =
+  listToMaybe
+    (mapMaybe (alternativeDiagonal triangulation) (undirectedEdges triangulation))
+
+alternativeDiagonal
+  :: Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> UndirectedEdgeId
+  -> Maybe ExactEdgeKey
+alternativeDiagonal triangulation edge
+  | isConstraintEdge triangulation edge = Nothing
+  | leftFace == outerFace || rightFace == outerFace = Nothing
+  | exactOrient2d c d b == GT && exactOrient2d d c a == GT =
+      Just (canonicalEdgeKey c d)
+  | otherwise = Nothing
+ where
+  (forward, backward) = directedPair edge
+  leftFace = incidentFace triangulation forward
+  rightFace = incidentFace triangulation backward
+  a = seedExactPoint (vertexData triangulation (Dcel.origin triangulation forward))
+  b = seedExactPoint (vertexData triangulation (Dcel.origin triangulation backward))
+  c =
+    seedExactPoint
+      (vertexData triangulation (Dcel.origin triangulation (Dcel.previous triangulation forward)))
+  d =
+    seedExactPoint
+      (vertexData triangulation (Dcel.origin triangulation (Dcel.previous triangulation backward)))
+labelResidentFaces
+  :: (Ord leftLabel, Ord rightLabel)
+  => (leftLabel, rightLabel)
+  -> Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (Map FaceId (leftLabel, rightLabel))
+labelResidentFaces outsideLabels triangulation atomicByKey = do
+  labelled <-
+    descendFaceTransitions
+      triangulation
+      atomicByKey
+      (Map.singleton outerFace outsideLabels)
+      (Seq.singleton outerFace)
+  case
+    [ face
+    | face <- innerFaces triangulation
+    , Map.notMember face labelled
+    ] of
+    missing : _ ->
+      Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned missing))
+    [] -> Right (Map.delete outerFace labelled)
+
+seedDirectedEndpoints
+  :: Triangulation mode OverlayVertexSeed directed undirected face
+  -> DirectedEdgeId
+  -> ExactEdgeKey
+seedDirectedEndpoints triangulation edge =
+  ( seedExactPoint (vertexData triangulation (Dcel.origin triangulation edge))
+  , seedExactPoint (vertexData triangulation (Dcel.destination triangulation edge))
+  )
+
+swapTransition :: (label, label) -> (label, label)
+swapTransition (fromLabel, toLabel) = (toLabel, fromLabel)
+
+descendFaceTransitions
+  :: (Eq leftLabel, Eq rightLabel)
+  => Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)
+  -> Map FaceId (leftLabel, rightLabel)
+  -> Seq.Seq FaceId
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (Map FaceId (leftLabel, rightLabel))
+descendFaceTransitions triangulation atomicByKey labelled queued =
+  case Seq.viewl queued of
+    Seq.EmptyL -> Right labelled
+    face Seq.:< remaining ->
+      case Map.lookup face labelled of
+        Nothing ->
+          Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))
+        Just current -> do
+          (nextLabels, nextQueue) <-
+            foldM
+              (descendFaceTransition triangulation atomicByKey face current)
+              (labelled, remaining)
+              (faceDirectedEdges triangulation face)
+          descendFaceTransitions triangulation atomicByKey nextLabels nextQueue
+
+descendFaceTransition
+  :: (Eq leftLabel, Eq rightLabel)
+  => Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)
+  -> FaceId
+  -> (leftLabel, rightLabel)
+  -> (Map FaceId (leftLabel, rightLabel), Seq.Seq FaceId)
+  -> DirectedEdgeId
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (Map FaceId (leftLabel, rightLabel), Seq.Seq FaceId)
+descendFaceTransition triangulation atomicByKey source current (labelled, queued) directed = do
+  let edge = asUndirected directed
+      destinationFace = incidentFace triangulation (reverseEdge directed)
+  (leftTransition, rightTransition) <-
+    case Map.lookup (residentSeedEdgeKey triangulation edge) atomicByKey of
+      Nothing -> Right (Nothing, Nothing)
+      Just atomic -> orientedTransitions edge directed atomic
+  let
+      expected =
+        ( maybe (fst current) fst leftTransition
+        , maybe (snd current) fst rightTransition
+        )
+      derived =
+        ( maybe (fst current) snd leftTransition
+        , maybe (snd current) snd rightTransition
+        )
+  if current == expected
+    then
+      case Map.lookup destinationFace labelled of
+        Nothing ->
+          Right
+            ( Map.insert destinationFace derived labelled
+            , queued Seq.|> destinationFace
+            )
+        Just existing
+          | existing == derived -> Right (labelled, queued)
+          | otherwise ->
+              Left
+                ( OverlayArrangementInvalid
+                    ( OverlayResidentFaceLabelConflict
+                        destinationFace
+                        edge
+                        existing
+                        derived
+                    )
+                )
+    else
+      Left
+        ( OverlayArrangementInvalid
+            (OverlayTransitionSourceMismatch source edge current expected)
+        )
+ where
+  orientedTransitions edge candidateDirection atomic
+    | directedEndpoints == (atomicEdgeFrom atomic, atomicEdgeTo atomic) =
+        Right
+          ( atomicEdgeLeftTransition atomic
+          , atomicEdgeRightTransition atomic
+          )
+    | directedEndpoints == (atomicEdgeTo atomic, atomicEdgeFrom atomic) =
+        Right
+          ( swapTransition <$> atomicEdgeLeftTransition atomic
+          , swapTransition <$> atomicEdgeRightTransition atomic
+          )
+    | otherwise =
+        Left
+          ( OverlayProvenanceIncomplete
+              ( OverlayAtomicConstraintOrientationMismatch
+                  edge
+                  (atomicEdgeFrom atomic)
+                  (atomicEdgeTo atomic)
+              )
+          )
+   where
+    directedEndpoints = seedDirectedEndpoints triangulation candidateDirection
+
+residentComponentDrafts
+  :: (Ord leftLabel, Ord rightLabel)
+  => Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Map FaceId (leftLabel, rightLabel)
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      [ComponentDraft leftLabel rightLabel]
+residentComponentDrafts triangulation labels =
+  traverse convert (faceComponents triangulation (`Map.lookup` labels))
+ where
+  exactPointAt vertex =
+    Right (seedExactPoint (vertexData triangulation vertex))
+  convert (maybeLabels, component) = do
+    componentLabels <-
+      case maybeLabels of
+        Just value -> Right value
+        Nothing ->
+          case faceComponentFaces component of
+            face : _ -> Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))
+            [] -> Left (OverlayArrangementInvalid OverlayFaceComponentEmpty)
+    boundary <-
+      first
+        (OverlayRegionPublicationFailed . RegionBoundaryObstruction)
+        (componentBoundary triangulation component)
+    polygon <-
+      first OverlayRegionPublicationFailed
+        (polygonComponentFromBoundaryCoordinates exactPointAt boundary)
+    pure
+      ComponentDraft
+        { componentDraftLabels = componentLabels
+        , componentDraftFaces = component
+        , componentDraftPolygon = polygon
+        , componentDraftTouchesOuter = touchesOuter triangulation component
+        }
+
+touchesOuter
+  :: Triangulation mode vertex directed undirected face
+  -> FaceComponent
+  -> Bool
+touchesOuter triangulation component =
+  any
+    ( any ((== outerFace) . incidentFace triangulation . reverseEdge)
+        . faceDirectedEdges triangulation
+    )
+    (faceComponentFaces component)
+
+partitionDrafts
+  :: (Eq leftLabel, Eq rightLabel)
+  => (leftLabel, rightLabel)
+  -> [ComponentDraft leftLabel rightLabel]
+  -> ([ComponentDraft leftLabel rightLabel], [ComponentDraft leftLabel rightLabel])
+partitionDrafts outsidePair =
+  partition
+    (\draftComponent ->
+       componentDraftTouchesOuter draftComponent
+         && componentDraftLabels draftComponent == outsidePair)
+
+sortComponentDrafts
+  :: (Ord leftLabel, Ord rightLabel)
+  => [ComponentDraft leftLabel rightLabel]
+  -> [ComponentDraft leftLabel rightLabel]
+sortComponentDrafts =
+  sortBy
+    (\left right ->
+       compare
+         (componentDraftPolygon left, componentDraftLabels left)
+         (componentDraftPolygon right, componentDraftLabels right))
+
+numberBoundedComponents
+  :: (Ord leftLabel, Ord rightLabel)
+  => [ComponentDraft leftLabel rightLabel]
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      [(OverlayCellId, ComponentDraft leftLabel rightLabel)]
+numberBoundedComponents drafts =
+  case
+    [ componentDraftPolygon left
+    | (left, right) <- consecutivePairs ordered
+    , componentSignature left == componentSignature right
+    ] of
+    duplicate : _ ->
+      Left
+        ( OverlayArrangementInvalid
+            (OverlayDuplicateCellSignature duplicate)
+        )
+    [] ->
+      Right
+        ( zipWith
+            (\index component -> (OverlayCellId index, component))
+            [1 ..]
+            ordered
+        )
+ where
+  ordered = sortComponentDrafts drafts
+  componentSignature
+    :: ComponentDraft leftLabel' rightLabel'
+    -> (PolygonComponent, (leftLabel', rightLabel'))
+  componentSignature component =
+    (componentDraftPolygon component, componentDraftLabels component)
+
+-- | Trace the finite boundary cycles of the unbounded exact cell from atomic
+-- edges only. Resident hull edges and Delaunay diagonals are absent by
+-- construction; the DCEL contributes only the already-proved incident-cell
+-- gluing needed to orient each atomic edge with cell zero on its left.
+unboundedCellBoundaryLoops
+  :: Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)
+  -> Map FaceId OverlayCellId
+  -> Either (OverlayError leftLabel rightLabel) [ExactLoop]
+unboundedCellBoundaryLoops triangulation atomicByKey cellIdByFace = do
+  orientedEdges <-
+    Set.fromList . concat
+      <$> traverse orientAtomicEdge (undirectedEdges triangulation)
+  traceBoundaryCycles orientedEdges
+ where
+  orientAtomicEdge edge
+    | Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey = Right []
+    | otherwise = do
+        let (forward, backward) = directedPair edge
+        forwardCell <- faceCellId (incidentFace triangulation forward)
+        backwardCell <- faceCellId (incidentFace triangulation backward)
+        pure
+          ( case (forwardCell == OverlayCellId 0, backwardCell == OverlayCellId 0) of
+              (True, False) -> [seedDirectedEndpoints triangulation forward]
+              (False, True) -> [seedDirectedEndpoints triangulation backward]
+              _ -> []
+          )
+  faceCellId face
+    | face == outerFace = Right (OverlayCellId 0)
+    | otherwise =
+        case Map.lookup face cellIdByFace of
+          Just cellId -> Right cellId
+          Nothing -> Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))
+
+traceBoundaryCycles
+  :: Set ExactEdgeKey
+  -> Either (OverlayError leftLabel rightLabel) [ExactLoop]
+traceBoundaryCycles orientedEdges = descend orientedEdges []
+ where
+  outgoing =
+    Map.mapWithKey orderAroundOrigin
+      ( Set.foldl'
+          (\byOrigin edge@(from, _) -> Map.insertWith (<>) from [edge] byOrigin)
+          Map.empty
+          orientedEdges
+      )
+  orderAroundOrigin :: ExactPoint -> [ExactEdgeKey] -> [ExactEdgeKey]
+  orderAroundOrigin origin =
+    sortBy (\(_, left) (_, right) -> compareAround origin left right)
+  descend remaining cycles =
+    case Set.lookupMin remaining of
+      Nothing -> Right (sort cycles)
+      Just seed ->
+        let (untraced, circuit) = boundaryEulerCircuit outgoing remaining (fst seed)
+         in case splitBoundaryCircuit circuit of
+          Nothing ->
+            Left
+              ( OverlayArrangementInvalid
+                  (uncurry OverlayCellCycleDidNotClose seed)
+              )
+          Just pointCycles -> do
+            loops <-
+              traverse
+                ( first
+                    (OverlayRegionPublicationFailed . RegionValidationObstruction)
+                    . exactLoop
+                )
+                pointCycles
+            descend untraced (loops <> cycles)
+
+-- | Consume one directed Eulerian boundary component. Each edge is deleted
+-- exactly once; circuit extraction therefore cannot acquire the exponential
+-- branch factor of simple-cycle backtracking at point contacts.
+boundaryEulerCircuit
+  :: Map ExactPoint [ExactEdgeKey]
+  -> Set ExactEdgeKey
+  -> ExactPoint
+  -> (Set ExactEdgeKey, [ExactEdgeKey])
+boundaryEulerCircuit outgoing remaining start =
+  descend remaining [start] [] []
+ where
+  descend untraced vertexStack incomingEdges circuit =
+    case vertexStack of
+      [] -> (untraced, circuit)
+      vertex : previousVertices ->
+        case nextUntracedEdge vertex untraced of
+          Just edge ->
+            descend
+              (Set.delete edge untraced)
+              (snd edge : vertexStack)
+              (edge : incomingEdges)
+              circuit
+          Nothing ->
+            case incomingEdges of
+              edge : previousEdges ->
+                descend
+                  untraced
+                  previousVertices
+                  previousEdges
+                  (edge : circuit)
+              [] -> (untraced, circuit)
+  nextUntracedEdge vertex untraced =
+    case
+        [ edge
+        | edge <- Map.findWithDefault [] vertex outgoing
+        , Set.member edge untraced
+        ] of
+      edge : _ -> Just edge
+      [] -> Nothing
+
+-- | Split an Euler circuit at repeated vertices. The maintained path is
+-- simple; closing against any resident path vertex emits one simple cycle and
+-- deletes precisely that suffix before descent continues.
+splitBoundaryCircuit :: [ExactEdgeKey] -> Maybe [NonEmpty ExactPoint]
+splitBoundaryCircuit circuit =
+  case circuit of
+    [] -> Nothing
+    (start, _) : _ ->
+      reverse
+        <$> descend
+          start
+          0
+          (Map.singleton start 0)
+          [start]
+          []
+          []
+          circuit
+ where
+  descend
+    :: ExactPoint
+    -> Int
+    -> Map ExactPoint Int
+    -> [ExactPoint]
+    -> [ExactEdgeKey]
+    -> [NonEmpty ExactPoint]
+    -> [ExactEdgeKey]
+    -> Maybe [NonEmpty ExactPoint]
+  descend
+    current
+    currentDepth
+    depthByPoint
+    reversedPathPoints
+    reversedPathEdges
+    cycles
+    remainingEdges =
+      case remainingEdges of
+        []
+          | currentDepth == 0
+          , [_] <- reversedPathPoints
+          , null reversedPathEdges -> Just cycles
+          | otherwise -> Nothing
+        edge@(fromPoint, toPoint) : rest
+          | fromPoint /= current -> Nothing
+          | otherwise ->
+              case Map.lookup toPoint depthByPoint of
+                Nothing ->
+                  descend
+                    toPoint
+                    (currentDepth + 1)
+                    (Map.insert toPoint (currentDepth + 1) depthByPoint)
+                    (toPoint : reversedPathPoints)
+                    (edge : reversedPathEdges)
+                    cycles
+                    rest
+                Just repeatedDepth ->
+                  let cyclePathLength = currentDepth - repeatedDepth
+                      removedPoints = take cyclePathLength reversedPathPoints
+                      cycleEdges = reverse (edge : take cyclePathLength reversedPathEdges)
+                      remainingPathPoints = drop cyclePathLength reversedPathPoints
+                      remainingPathEdges = drop cyclePathLength reversedPathEdges
+                      remainingDepths =
+                        foldr Map.delete depthByPoint removedPoints
+                   in case NonEmpty.nonEmpty (map fst cycleEdges) of
+                        Nothing -> Nothing
+                        Just cyclePoints ->
+                          descend
+                            toPoint
+                            repeatedDepth
+                            remainingDepths
+                            remainingPathPoints
+                            remainingPathEdges
+                            (cyclePoints : cycles)
+                            rest
+
+attachFaceCells
+  :: Map FaceId OverlayCellId
+  -> Triangulation 'Constrained OverlayVertexSeed () () ()
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (Triangulation 'Constrained OverlayVertexSeed () () OverlayFace)
+attachFaceCells cellIdByFace triangulation = do
+  payloads <- V.fromList <$> traverse facePayload (allFaces triangulation)
+  let outerPayload = OverlayFace (OverlayCellId 0)
+      defaults = triElementDefaults triangulation
+  pure
+    triangulation
+      { triFaceData = boxedFromVector (Just outerPayload) payloads
+      , triElementDefaults = defaults{defaultFaceData = outerPayload}
+      }
+ where
+  facePayload face
+    | face == outerFace = Right (OverlayFace (OverlayCellId 0))
+    | otherwise =
+        case Map.lookup face cellIdByFace of
+          Just cellId -> Right (OverlayFace cellId)
+          Nothing ->
+            Left
+              ( OverlayProvenanceIncomplete
+                  (OverlayResidentFaceUnassigned face)
+              )
+
+attachOverlayVertices
+  :: Triangulation 'Constrained OverlayVertexSeed () () face
+  -> Triangulation 'Constrained OverlayVertex () () face
+attachOverlayVertices =
+  Dcel.mapVertices
+    (\seed -> OverlayVertex (seedExactPoint seed) (seedOrigin seed))
+
+cellPayload
+  :: V.Vector (OverlayCell leftLabel rightLabel)
+  -> OverlayCellId
+  -> Either OverlayCellWitness (OverlayCell leftLabel rightLabel)
+cellPayload cells cellId@(OverlayCellId index) =
+  case cells V.!? index of
+    Just cell -> Right cell
+    Nothing -> Left (OverlayCellPayloadMissing cellId)
+
+faceLabels
+  :: OverlayResult leftLabel rightLabel
+  -> FaceId
+  -> Either OverlayCellWitness (leftLabel, rightLabel)
+faceLabels result face = do
+  cell <-
+    cellPayload
+      (overlayResultCells result)
+      (overlayFaceCellId (faceData (overlayResultTriangulation result) face))
+  pure (overlayCellLeft cell, overlayCellRight cell)
+
+regionFaceLabels
+  :: OverlayResult leftLabel rightLabel
+  -> FaceId
+  -> Either RegionPublicationError (leftLabel, rightLabel)
+regionFaceLabels result face =
+  case faceLabels result face of
+    Right labels -> Right labels
+    Left _ -> Left (RegionFaceLabelMissing face)
+
+vertexSupport
+  :: (Ord leftLabel, Ord rightLabel)
+  => OverlayResult leftLabel rightLabel
+  -> VertexId
+  -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)
+vertexSupport result vertex = do
+  labels <-
+    traverse
+      (faceLabels result . incidentFace triangulation)
+      (vertexOutgoingEdges triangulation vertex)
+  case supportFromPairs labels of
+    Just support -> Right support
+    Nothing -> Left (OverlayVertexSupportMissing vertex)
+ where
+  triangulation = overlayResultTriangulation result
+
+edgeSupport
+  :: (Ord leftLabel, Ord rightLabel)
+  => OverlayResult leftLabel rightLabel
+  -> UndirectedEdgeId
+  -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)
+edgeSupport result edge = do
+  pairs <- traverse (faceLabels result . incidentFace triangulation) [forward, backward]
+  case supportFromPairs pairs of
+    Just support -> Right support
+    Nothing -> Left (OverlayEdgeSupportMissing edge)
+ where
+  triangulation = overlayResultTriangulation result
+  (forward, backward) = directedPair edge
+
+supportFromPairs
+  :: (Ord leftLabel, Ord rightLabel)
+  => [(leftLabel, rightLabel)]
+  -> Maybe (OverlayCellSupport leftLabel rightLabel)
+supportFromPairs pairs = do
+  let (leftLabels, rightLabels) =
+        foldl'
+          (\(left, right) (leftLabel, rightLabel) ->
+             (Set.insert leftLabel left, Set.insert rightLabel right))
+          (Set.empty, Set.empty)
+          pairs
+  leftSupport <- nonEmptySupport leftLabels
+  rightSupport <- nonEmptySupport rightLabels
+  pure (OverlayCellSupport leftSupport rightSupport)
+
+nonEmptySupport :: Set label -> Maybe (OverlaySupport label)
+nonEmptySupport labels = OverlaySupport <$> NonEmpty.nonEmpty (Set.toAscList labels)
+
+validateAtomicConstraints
+  :: Triangulation
+      'Constrained
+      OverlayVertexSeed
+      ()
+      ()
+      ()
+  -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)
+  -> Either (OverlayError leftLabel rightLabel) ()
+validateAtomicConstraints triangulation atomicByKey = do
+  traverse_ requireAtomic (Map.keys atomicByKey)
+  traverse_ requireExpectedConstraint (undirectedEdges triangulation)
+ where
+  residentByKey =
+    Map.fromList
+      [ (residentSeedEdgeKey triangulation edge, edge)
+      | edge <- undirectedEdges triangulation
+      ]
+  requireAtomic key@(from, to) =
+    case Map.lookup key residentByKey of
+      Nothing -> Left (OverlayProvenanceIncomplete (OverlayAtomicConstraintMissing from to))
+      Just edge
+        | isConstraintEdge triangulation edge -> Right ()
+        | otherwise -> Left (OverlayProvenanceIncomplete (OverlayBoundaryEdgeNotConstrained edge))
+  requireExpectedConstraint edge
+    | isConstraintEdge triangulation edge
+        && Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey =
+        Left (OverlayProvenanceIncomplete (OverlayUnexpectedConstraint edge))
+    | otherwise = Right ()
+
+residentSeedEdgeKey
+  :: Triangulation mode OverlayVertexSeed directed undirected face
+  -> UndirectedEdgeId
+  -> ExactEdgeKey
+residentSeedEdgeKey = residentEdgeKeyBy seedExactPoint
+
+residentEdgeKey
+  :: Triangulation mode OverlayVertex directed undirected face
+  -> UndirectedEdgeId
+  -> ExactEdgeKey
+residentEdgeKey = residentEdgeKeyBy overlayExactPoint
+
+residentEdgeKeyBy
+  :: (vertex -> ExactPoint)
+  -> Triangulation mode vertex directed undirected face
+  -> UndirectedEdgeId
+  -> ExactEdgeKey
+residentEdgeKeyBy exactPointAt triangulation edge =
+  let (from, to) = undirectedEndpoints triangulation edge
+   in canonicalEdgeKey
+        (exactPointAt (vertexData triangulation from))
+        (exactPointAt (vertexData triangulation to))
diff --git a/src-build/Moonlight/Triangulation/Internal/Overlay/Types.hs b/src-build/Moonlight/Triangulation/Internal/Overlay/Types.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Internal/Overlay/Types.hs
@@ -0,0 +1,267 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE KindSignatures #-}
+
+-- | Closed vocabulary and opaque carrier representation for exact labelled
+-- overlay. Construction lives in the public build-tier module; this module
+-- exists so every invariant-bearing payload shares one owner.
+module Moonlight.Triangulation.Internal.Overlay.Types
+  ( BoundaryLoopRef (..)
+  , OverlayOperand (..)
+  , BoundaryVertexRef (..)
+  , BoundaryEdgeRef (..)
+  , OverlayVertexOrigin (..)
+  , OverlayEdgeOrigin (..)
+  , OverlaySupport (..)
+  , overlaySupportLabels
+  , OverlayCellSupport (..)
+  , OverlayCellId (..)
+  , OverlayCellGeometry (..)
+  , OverlayCell (..)
+  , OverlayFace (..)
+  , OverlayVertex (..)
+  , OverlayEdge (..)
+  , OverlayReceipt (..)
+  , OverlayArrangementObstruction (..)
+  , OverlayCellWitness (..)
+  , OverlayError (..)
+  , OverlayResult (..)
+  , OverlaySelectionKind (..)
+  , OverlaySelectionError (..)
+  ) where
+
+import Control.DeepSeq (NFData)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Vector (Vector)
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.CellSet (CellSelectionError)
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  )
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( FaceId
+  , UndirectedEdgeId
+  , VertexId
+  )
+import Moonlight.Triangulation.Internal.Cdt.Types (CdtError)
+import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError)
+import Moonlight.Triangulation.Internal.ExactSegmentEvents (ExactSegmentEventObstruction)
+import Moonlight.Triangulation.Internal.Overlay.Embedding (OverlayEmbeddingObstruction)
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop
+  , PolygonComponent
+  , RegionPublicationError
+  )
+import Moonlight.Triangulation.Internal.Representation (Triangulation)
+import Moonlight.Triangulation.Internal.Types
+  ( ConstraintMode (Constrained)
+  )
+
+-- | Which cycle inside a polygon component supplied a source reference.
+data BoundaryLoopRef
+  = BoundaryOuterLoop
+  | BoundaryHoleLoop !Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayOperand
+  = LeftOverlayOperand
+  | RightOverlayOperand
+
+-- | A boundary vertex reference whose phantom operand prevents left/right
+-- provenance from being interchanged while sharing their identical payload.
+data BoundaryVertexRef (operand :: OverlayOperand) = BoundaryVertexRef
+  { boundaryVertexComponent :: !Int
+  , boundaryVertexLoop :: !BoundaryLoopRef
+  , boundaryVertexLocalIndex :: !Int
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | The corresponding typed reference for one source boundary edge.
+data BoundaryEdgeRef (operand :: OverlayOperand) = BoundaryEdgeRef
+  { boundaryEdgeComponent :: !Int
+  , boundaryEdgeLoop :: !BoundaryLoopRef
+  , boundaryEdgeLocalIndex :: !Int
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Complete typed source closure of one arrangement vertex.
+data OverlayVertexOrigin = OverlayVertexOrigin
+  { overlayOriginLeftVertices :: ![BoundaryVertexRef 'LeftOverlayOperand]
+  , overlayOriginRightVertices :: ![BoundaryVertexRef 'RightOverlayOperand]
+  , overlayOriginLeftEdges :: ![BoundaryEdgeRef 'LeftOverlayOperand]
+  , overlayOriginRightEdges :: ![BoundaryEdgeRef 'RightOverlayOperand]
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Nonempty typed source-edge provenance of one exact atomic interval.
+data OverlayEdgeOrigin = OverlayEdgeOrigin
+  { overlayEdgeLeftSources :: ![BoundaryEdgeRef 'LeftOverlayOperand]
+  , overlayEdgeRightSources :: ![BoundaryEdgeRef 'RightOverlayOperand]
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Sorted nonempty labels whose closures contain one relatively open cell.
+newtype OverlaySupport label = OverlaySupport (NonEmpty label)
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+overlaySupportLabels :: OverlaySupport label -> NonEmpty label
+overlaySupportLabels (OverlaySupport labels) = labels
+
+data OverlayCellSupport leftLabel rightLabel = OverlayCellSupport
+  { overlaySupportLeft :: !(OverlaySupport leftLabel)
+  , overlaySupportRight :: !(OverlaySupport rightLabel)
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+newtype OverlayCellId = OverlayCellId Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayCellGeometry
+  = BoundedOverlayCell !PolygonComponent
+  | UnboundedOverlayCell ![ExactLoop]
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayCell leftLabel rightLabel = OverlayCell
+  { overlayCellLeft :: !leftLabel
+  , overlayCellRight :: !rightLabel
+  , overlayCellGeometry :: !OverlayCellGeometry
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A resident face stores only its authoritative dense-cell reference.
+-- Labels and geometry belong to 'OverlayCell'; duplicating them here made
+-- every observation carry a reconciliation obligation with no additional law.
+newtype OverlayFace = OverlayFace
+  { overlayFaceCellId :: OverlayCellId
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Exact geometry and provenance are intrinsic to a vertex. Label support is
+-- the finite union of incident cell descriptors and is therefore a view.
+data OverlayVertex = OverlayVertex
+  { overlayExactPoint :: !ExactPoint
+  , overlayVertexOrigin :: !OverlayVertexOrigin
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Boundary provenance is intrinsic; label support is derived from the two
+-- incident face-cell references.
+data OverlayEdge
+  = OverlayBoundary !OverlayEdgeOrigin
+  | OverlayDiagonal
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayReceipt = OverlayReceipt
+  { overlayInputSegments :: !Int
+  , overlayRelationEvents :: !Int
+  , overlayExactCrossings :: !Int
+  , overlayOverlapIntervals :: !Int
+  , overlayAtomicEdges :: !Int
+  , overlayOutputVertices :: !Int
+  , overlayArrangementCells :: !Int
+  , overlayResidentFaces :: !Int
+  , overlayEmbeddingCandidates :: !Int
+  , overlayTotalRelationChecks :: !Int
+  , overlaySweepMaximumHeight :: !Int
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayArrangementObstruction leftLabel rightLabel
+  = OverlayLeftSourceSideConflict
+      !OverlayEdgeOrigin
+      !(NonEmpty leftLabel)
+  | OverlayRightSourceSideConflict
+      !OverlayEdgeOrigin
+      !(NonEmpty rightLabel)
+  | OverlayRotationDegenerate !ExactPoint
+  | OverlayFaceComponentEmpty
+  | OverlayCellCycleDidNotClose !ExactPoint !ExactPoint
+  | OverlayTransitionSourceMismatch
+      !FaceId
+      !UndirectedEdgeId
+      !(leftLabel, rightLabel)
+      !(leftLabel, rightLabel)
+  | OverlayResidentFaceLabelConflict
+      !FaceId
+      !UndirectedEdgeId
+      !(leftLabel, rightLabel)
+      !(leftLabel, rightLabel)
+  | OverlayDuplicateCellSignature !PolygonComponent
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayCellWitness
+  = OverlayAtomicConstraintMissing !ExactPoint !ExactPoint
+  | OverlayAtomicConstraintOrientationMismatch
+      !UndirectedEdgeId
+      !ExactPoint
+      !ExactPoint
+  | OverlayUnexpectedConstraint !UndirectedEdgeId
+  | OverlayBoundaryEdgeNotConstrained !UndirectedEdgeId
+  | OverlayResidentFaceUnassigned !FaceId
+  | OverlayCellPayloadMissing !OverlayCellId
+  | OverlayVertexSupportMissing !VertexId
+  | OverlayEdgeSupportMissing !UndirectedEdgeId
+  | OverlayEmbeddedVertexCountMismatch !Int !Int
+  | OverlayExactVertexMissing !ExactPoint
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlayError leftLabel rightLabel
+  = OverlayExactArithmetic !ExactArithmeticError
+  | OverlaySegmentEventsInvalid !ExactSegmentEventObstruction
+  | OverlayArrangementInvalid !(OverlayArrangementObstruction leftLabel rightLabel)
+  | OverlayEmbeddingRefused !(NonEmpty OverlayEmbeddingObstruction)
+  | OverlayBuildFailed !CdtError
+  | OverlayRegionPublicationFailed !RegionPublicationError
+  | OverlayProvenanceIncomplete !OverlayCellWitness
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | The singular exact subdivision carrier. Its embedded DCEL is a derived
+-- binary64 realization; exact coordinates remain in the vertex payload plane.
+data OverlayResult leftLabel rightLabel = OverlayResult
+  { overlayResultTriangulation
+      :: !( Triangulation
+              'Constrained
+              OverlayVertex
+              ()
+              OverlayEdge
+              OverlayFace
+          )
+  , overlayResultCells :: !(Vector (OverlayCell leftLabel rightLabel))
+  , overlayResultOutsideLabels :: !(leftLabel, rightLabel)
+  , overlayResultReceipt :: !OverlayReceipt
+  }
+  deriving stock (Generic)
+  deriving anyclass (NFData)
+
+data OverlaySelectionKind
+  = ClosedUnionSelection
+  | ClosedIntersectionSelection
+  | RegularizedDifferenceSelection
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data OverlaySelectionError
+  = OverlaySelectionContainsUnboundedCell !OverlaySelectionKind
+  | OverlaySelectionProvenance !OverlayCellWitness
+  | OverlaySelectionInvalid !CellSelectionError
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
diff --git a/src-build/Moonlight/Triangulation/Minkowski.hs b/src-build/Moonlight/Triangulation/Minkowski.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Minkowski.hs
@@ -0,0 +1,583 @@
+{-# LANGUAGE DataKinds #-}
+
+-- | Exact polygonal Minkowski addition and regularized two-dimensional
+-- morphology. Convex convolution is direct; nonconvex construction descends
+-- through the existing exact overlay, resident CDT, and grouped publication
+-- owners. Lower-dimensional erosion residuals cannot inhabit 'PlanarRegion'
+-- and therefore publish as empty rather than being forged as polygons.
+module Moonlight.Triangulation.Minkowski
+  ( ConvexPolygon
+  , convexPolygon
+  , convexPolygonPoints
+  , StructuringElement
+  , structuringElement
+  , MinkowskiOperation (..)
+  , MinkowskiError (..)
+  , MinkowskiReceipt (..)
+  , convexMinkowskiSum
+  , minkowskiSum
+  , erodeBy
+  , openWith
+  , closeWith
+  , polygonOffset
+  , polygonInset
+  ) where
+
+import Control.Applicative ((<|>))
+import Control.Monad (filterM)
+import Data.Bifunctor (first)
+import qualified Data.IntMap.Strict as IntMap
+import qualified Data.Map.Strict as Map
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import Data.Maybe (fromMaybe)
+import qualified Data.Set as Set
+import qualified Data.Vector as V
+import Moonlight.Triangulation.Dcel
+  ( faceData
+  , faceVertices
+  , vertexData
+  )
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , exactPointCoordinates
+  )
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( FaceId
+  )
+import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational
+  , exactRationalDenominator
+  , exactRationalNumerator
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)
+import Moonlight.Triangulation.Internal.Dyadic (integerBitLength)
+import Moonlight.Triangulation.Internal.Minkowski.Convex
+  ( addExactPoints
+  , admittedConvexLoop
+  , convexHullPolygon
+  , convexMinkowskiPolygon
+  , convexMinkowskiSum
+  , convexPolygon
+  , convexPolygonCentroid
+  , convexPolygonPoints
+  , convexPolygonRegion
+  , erodeConvexBy
+  , reflectConvexPolygon
+  , structuringElement
+  , structuringElementPolygon
+  )
+import Moonlight.Triangulation.Internal.Minkowski.Types
+import Moonlight.Triangulation.Internal.Overlay.Resident (faceLabels)
+import Moonlight.Triangulation.Internal.Overlay.Types
+  ( OverlayCell (..)
+  , OverlayCellGeometry (..)
+  , OverlayCellId (..)
+  , OverlayFace (..)
+  , OverlayResult (..)
+  , OverlayVertex (..)
+  )
+import Moonlight.Triangulation.Overlay
+  ( OverlayReceipt (..)
+  , overlayLayers
+  , overlayReceipt
+  , overlaySelectedRegion
+  )
+import Moonlight.Triangulation.Region
+  ( PlanarLayer
+  , PlanarRegion
+  , PolygonComponent
+  , RegionPointLocation (..)
+  , emptyPlanarRegion
+  , exactLoopPoints
+  , planarLayerRegions
+  , planarRegionComponents
+  , polygonHoleLoops
+  , polygonOuterLoop
+  , regionPointLocation
+  )
+import Moonlight.Triangulation.Internal.Region.Publication
+  ( labelledPlanarLayerFromExactCoordinates
+  , planarLayerFromAdmittedComponents
+  )
+
+data MorphologyMetrics = MorphologyMetrics
+  { metricOverlayPasses :: !Int
+  , metricExactCrossings :: !Int
+  , metricOutputCells :: !(Maybe Int)
+  }
+
+emptyMetrics :: MorphologyMetrics
+emptyMetrics = MorphologyMetrics 0 0 Nothing
+
+appendMetrics :: MorphologyMetrics -> MorphologyMetrics -> MorphologyMetrics
+appendMetrics left right =
+  MorphologyMetrics
+    { metricOverlayPasses = metricOverlayPasses left + metricOverlayPasses right
+    , metricExactCrossings = metricExactCrossings left + metricExactCrossings right
+    , metricOutputCells = metricOutputCells right <|> metricOutputCells left
+    }
+
+minkowskiSum
+  :: PlanarRegion
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+minkowskiSum left right = do
+  (leftPieces, leftMetrics) <- decomposeRegion left
+  (rightPieces, rightMetrics) <- decomposeRegion right
+  let generated =
+        [ convexMinkowskiPolygon leftPiece rightPiece
+        | leftPiece <- leftPieces
+        , rightPiece <- rightPieces
+        ]
+      generatedRegions = map convexPolygonRegion generated
+      convolutionEdges = sum (map (NonEmpty.length . convexPolygonPoints) generated)
+  (result, unionMetrics) <- unionRegions generatedRegions
+  let metrics = leftMetrics `appendMetrics` rightMetrics `appendMetrics` unionMetrics
+  pure
+    ( result
+    , MinkowskiReceipt
+        { minkowskiOperation = MinkowskiAddition
+        , minkowskiInputComponents =
+            length (planarRegionComponents left)
+              + length (planarRegionComponents right)
+        , minkowskiConvexPieces = length leftPieces + length rightPieces
+        , minkowskiGeneratedPieces = length generated
+        , minkowskiGeneratedConvolutionEdges = convolutionEdges
+        , minkowskiOverlayPasses = metricOverlayPasses metrics
+        , minkowskiExactCrossings = metricExactCrossings metrics
+        , minkowskiOutputCells = fromMaybe 0 (metricOutputCells metrics)
+        , minkowskiExactCoordinateBitGrowth =
+            coordinateBitGrowth [left, right] result
+        }
+    )
+
+-- | Erode a polygonal region by an origin-anchored convex kernel and publish
+-- the regularized full-dimensional result. A residual consisting only of
+-- points or segments is represented by 'emptyPlanarRegion'.
+erodeBy
+  :: StructuringElement
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+erodeBy element source =
+  case singleConvexRegion source of
+    Just sourcePolygon -> convexErosion element source sourcePolygon
+    Nothing -> generalErosion element source
+
+openWith
+  :: StructuringElement
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+openWith element source = do
+  (eroded, erosionReceipt) <- erodeBy element source
+  (opened, additionReceipt) <- polygonOffset element eroded
+  pure
+    ( opened
+    , composeReceipts
+        MinkowskiOpening
+        (length (planarRegionComponents source))
+        erosionReceipt
+        additionReceipt
+    )
+
+closeWith
+  :: StructuringElement
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+closeWith element source = do
+  (expanded, additionReceipt) <- polygonOffset element source
+  (closed, erosionReceipt) <- erodeBy element expanded
+  pure
+    ( closed
+    , composeReceipts
+        MinkowskiClosing
+        (length (planarRegionComponents source))
+        additionReceipt
+        erosionReceipt
+    )
+
+polygonOffset
+  :: StructuringElement
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+polygonOffset element source =
+  minkowskiSum source (convexPolygonRegion (structuringElementPolygon element))
+
+polygonInset
+  :: StructuringElement
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+polygonInset = erodeBy
+
+decomposeRegion
+  :: PlanarRegion
+  -> Either MinkowskiError ([ConvexPolygon], MorphologyMetrics)
+decomposeRegion region =
+  case traverse convexComponent (planarRegionComponents region) of
+    Just convexPieces -> Right (convexPieces, emptyMetrics)
+    Nothing -> triangulatedPieces region
+
+convexComponent :: PolygonComponent -> Maybe ConvexPolygon
+convexComponent component =
+  case polygonHoleLoops component of
+    [] -> admittedConvexLoop (polygonOuterLoop component)
+    _ -> Nothing
+
+singleConvexRegion :: PlanarRegion -> Maybe ConvexPolygon
+singleConvexRegion region =
+  case planarRegionComponents region of
+    [component] -> convexComponent component
+    _ -> Nothing
+
+triangulatedPieces
+  :: PlanarRegion
+  -> Either MinkowskiError ([ConvexPolygon], MorphologyMetrics)
+triangulatedPieces region = do
+  let sourceLayer = morphologyLayer region
+  result <- first MinkowskiOverlayFailed (overlayLayers sourceLayer emptyMorphologyLayer)
+  selectedFaces <-
+    filterM
+      ( fmap fst
+          . first MinkowskiOverlayCellWitness
+          . faceLabels result
+      )
+      (innerFaces (overlayResultTriangulation result))
+  pieces <- traverse (faceConvexPolygon result) selectedFaces
+  pure (pieces, metricsFromOverlay result (length pieces))
+
+faceConvexPolygon
+  :: OverlayResult leftLabel rightLabel
+  -> FaceId
+  -> Either MinkowskiError ConvexPolygon
+faceConvexPolygon result face =
+  case
+    map
+      (overlayExactPoint . vertexData triangulation)
+      (faceVertices triangulation face) of
+    [firstPoint, secondPoint, thirdPoint] ->
+      convexHullPolygon (firstPoint :| [secondPoint, thirdPoint])
+    vertices -> Left (MinkowskiFaceArity face (length vertices))
+ where
+  triangulation = overlayResultTriangulation result
+
+unionRegions
+  :: [PlanarRegion]
+  -> Either MinkowskiError (PlanarRegion, MorphologyMetrics)
+unionRegions [] = Right (emptyPlanarRegion, emptyMetrics)
+unionRegions [region] =
+  Right
+    ( region
+    , emptyMetrics{metricOutputCells = Just (length (planarRegionComponents region))}
+    )
+unionRegions regions = do
+  let (leftRegions, rightRegions) = splitAt (length regions `div` 2) regions
+  left <- unionRegions leftRegions
+  right <- unionRegions rightRegions
+  glueRegionUnion left right
+
+glueRegionUnion
+  :: (PlanarRegion, MorphologyMetrics)
+  -> (PlanarRegion, MorphologyMetrics)
+  -> Either MinkowskiError (PlanarRegion, MorphologyMetrics)
+glueRegionUnion (left, leftMetrics) (right, rightMetrics)
+  | null (planarRegionComponents left) = Right (right, leftMetrics `appendMetrics` rightMetrics)
+  | null (planarRegionComponents right) = Right (left, leftMetrics `appendMetrics` rightMetrics)
+  | otherwise = do
+      result <-
+        first MinkowskiOverlayFailed
+          (overlayLayers (morphologyLayer left) (morphologyLayer right))
+      published <-
+        first MinkowskiPublicationFailed
+          ( overlaySelectedRegion
+              (uncurry (||))
+              result
+          )
+      let selectedCells =
+            V.foldl'
+              (\count cell ->
+                 case overlayCellGeometry cell of
+                   BoundedOverlayCell _
+                     | overlayCellLeft cell || overlayCellRight cell ->
+                         count + 1
+                   _ -> count)
+              0
+              (overlayResultCells result)
+      pure
+        ( published
+        , leftMetrics
+            `appendMetrics` rightMetrics
+            `appendMetrics` metricsFromOverlay result selectedCells
+        )
+
+convexErosion
+  :: StructuringElement
+  -> PlanarRegion
+  -> ConvexPolygon
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+convexErosion element source sourcePolygon = do
+  eroded <- erodeConvexBy sourcePolygon (structuringElementPolygon element)
+  let result = maybe emptyPlanarRegion convexPolygonRegion eroded
+      outputCells = maybe 0 (const 1) eroded
+      generatedEdges = maybe 0 (NonEmpty.length . convexPolygonPoints) eroded
+  pure
+    ( result
+    , MinkowskiReceipt
+        { minkowskiOperation = MinkowskiErosion
+        , minkowskiInputComponents = 1
+        , minkowskiConvexPieces = 2
+        , minkowskiGeneratedPieces = outputCells
+        , minkowskiGeneratedConvolutionEdges = generatedEdges
+        , minkowskiOverlayPasses = 0
+        , minkowskiExactCrossings = 0
+        , minkowskiOutputCells = outputCells
+        , minkowskiExactCoordinateBitGrowth =
+            coordinateBitGrowth
+              [ source
+              , convexPolygonRegion (structuringElementPolygon element)
+              ]
+              result
+        }
+    )
+
+generalErosion
+  :: StructuringElement
+  -> PlanarRegion
+  -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+generalErosion element source
+  | null sourceEdges = Right (emptyPlanarRegion, emptyErosionReceipt)
+  | otherwise = do
+      sweptPolygons <- traverse (sweepBoundaryEdge reflectedKernel) sourceEdges
+      let sweptRegions = map convexPolygonRegion sweptPolygons
+          generatedEdges =
+            sum (map (NonEmpty.length . convexPolygonPoints) sweptPolygons)
+      (contactRegion, unionMetrics) <- unionRegions sweptRegions
+      candidateOverlay <-
+        first MinkowskiOverlayFailed
+          (overlayLayers (morphologyLayer contactRegion) emptyMorphologyLayer)
+      kernelWitness <- convexPolygonCentroid kernel
+      let representativeFaceByCell = representativeFaces candidateOverlay
+      selectedCellIds <-
+        Set.fromList
+          <$> filterM
+            ( classifyCandidateCell
+                source
+                kernelWitness
+                representativeFaceByCell
+                candidateOverlay
+            )
+            (boundedOutsideCellIds candidateOverlay)
+      published <- publishCellSelection selectedCellIds candidateOverlay
+      let candidateMetrics =
+            metricsFromOverlay candidateOverlay (Set.size selectedCellIds)
+          metrics = unionMetrics `appendMetrics` candidateMetrics
+      pure
+        ( published
+        , MinkowskiReceipt
+            { minkowskiOperation = MinkowskiErosion
+            , minkowskiInputComponents = length (planarRegionComponents source)
+            , minkowskiConvexPieces = 1
+            , minkowskiGeneratedPieces = length sweptPolygons
+            , minkowskiGeneratedConvolutionEdges = generatedEdges
+            , minkowskiOverlayPasses = metricOverlayPasses metrics
+            , minkowskiExactCrossings = metricExactCrossings metrics
+            , minkowskiOutputCells = Set.size selectedCellIds
+            , minkowskiExactCoordinateBitGrowth =
+                coordinateBitGrowth
+                  [source, convexPolygonRegion kernel]
+                  published
+            }
+        )
+ where
+  kernel = structuringElementPolygon element
+  reflectedKernel = reflectConvexPolygon kernel
+  sourceEdges = regionBoundaryEdges source
+  emptyErosionReceipt =
+    MinkowskiReceipt
+      { minkowskiOperation = MinkowskiErosion
+      , minkowskiInputComponents = 0
+      , minkowskiConvexPieces = 1
+      , minkowskiGeneratedPieces = 0
+      , minkowskiGeneratedConvolutionEdges = 0
+      , minkowskiOverlayPasses = 0
+      , minkowskiExactCrossings = 0
+      , minkowskiOutputCells = 0
+      , minkowskiExactCoordinateBitGrowth = 0
+      }
+
+sweepBoundaryEdge
+  :: ConvexPolygon
+  -> (ExactPoint, ExactPoint)
+  -> Either MinkowskiError ConvexPolygon
+sweepBoundaryEdge reflectedKernel (from, to) =
+  case convexPolygonPoints reflectedKernel of
+    firstKernelPoint :| remainingKernelPoints ->
+      convexHullPolygon
+        ( addExactPoints from firstKernelPoint
+            :| ( map (addExactPoints from) remainingKernelPoints
+                   <> map (addExactPoints to) kernelPoints
+               )
+        )
+ where
+  kernelPoints = NonEmpty.toList (convexPolygonPoints reflectedKernel)
+
+boundedOutsideCellIds
+  :: OverlayResult Bool Bool
+  -> [OverlayCellId]
+boundedOutsideCellIds result =
+  V.ifoldr
+    (\index cell selected ->
+       case overlayCellGeometry cell of
+         BoundedOverlayCell _
+           | not (overlayCellLeft cell)
+               && not (overlayCellRight cell) ->
+               OverlayCellId index : selected
+         _ -> selected)
+    []
+    (overlayResultCells result)
+
+classifyCandidateCell
+  :: PlanarRegion
+  -> ExactPoint
+  -> IntMap.IntMap FaceId
+  -> OverlayResult Bool Bool
+  -> OverlayCellId
+  -> Either MinkowskiError Bool
+classifyCandidateCell source kernelWitness representativeFaceByCell result cellId = do
+  face <-
+    maybe
+      (Left (MinkowskiCandidateCellMissing cellId))
+      Right
+      (IntMap.lookup (overlayCellIndex cellId) representativeFaceByCell)
+  candidate <- convexPolygonCentroid =<< faceConvexPolygon result face
+  let inclusionWitness = addExactPoints candidate kernelWitness
+  case regionPointLocation source inclusionWitness of
+    RegionInterior -> Right True
+    RegionExterior -> Right False
+    RegionOnBoundary -> Left (MinkowskiInclusionAmbiguous cellId inclusionWitness)
+
+representativeFaces
+  :: OverlayResult leftLabel rightLabel
+  -> IntMap.IntMap FaceId
+representativeFaces result =
+  IntMap.fromListWith min
+    [ (overlayCellIndex (overlayFaceCellId (faceData triangulation face)), face)
+    | face <- innerFaces triangulation
+    ]
+ where
+  triangulation = overlayResultTriangulation result
+
+overlayCellIndex :: OverlayCellId -> Int
+overlayCellIndex (OverlayCellId index) = index
+
+publishCellSelection
+  :: Set.Set OverlayCellId
+  -> OverlayResult leftLabel rightLabel
+  -> Either MinkowskiError PlanarRegion
+publishCellSelection selected result = do
+  layer <-
+    first MinkowskiPublicationFailed
+      ( labelledPlanarLayerFromExactCoordinates
+          False
+          triangulation
+          (\vertex -> Right (overlayExactPoint (vertexData triangulation vertex)))
+          (\face ->
+             Right
+               ( Set.member
+                   (overlayFaceCellId (faceData triangulation face))
+                   selected
+               ))
+      )
+  pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions layer))
+ where
+  triangulation = overlayResultTriangulation result
+
+morphologyLayer
+  :: PlanarRegion
+  -> PlanarLayer Bool
+morphologyLayer region =
+  planarLayerFromAdmittedComponents
+    False
+    [(True, component) | component <- planarRegionComponents region]
+
+emptyMorphologyLayer :: PlanarLayer Bool
+emptyMorphologyLayer = morphologyLayer emptyPlanarRegion
+
+regionBoundaryEdges :: PlanarRegion -> [(ExactPoint, ExactPoint)]
+regionBoundaryEdges region =
+  concatMap
+    (\component ->
+       concatMap
+         (cyclePairs . exactLoopPoints)
+         (polygonOuterLoop component : polygonHoleLoops component))
+    (planarRegionComponents region)
+
+metricsFromOverlay
+  :: OverlayResult leftLabel rightLabel
+  -> Int
+  -> MorphologyMetrics
+metricsFromOverlay result outputCells =
+  MorphologyMetrics
+    { metricOverlayPasses = 1
+    , metricExactCrossings = overlayExactCrossings (overlayReceipt result)
+    , metricOutputCells = Just outputCells
+    }
+
+composeReceipts
+  :: MinkowskiOperation
+  -> Int
+  -> MinkowskiReceipt
+  -> MinkowskiReceipt
+  -> MinkowskiReceipt
+composeReceipts operation inputComponents firstReceipt secondReceipt =
+  MinkowskiReceipt
+    { minkowskiOperation = operation
+    , minkowskiInputComponents = inputComponents
+    , minkowskiConvexPieces =
+        minkowskiConvexPieces firstReceipt
+          + minkowskiConvexPieces secondReceipt
+    , minkowskiGeneratedPieces =
+        minkowskiGeneratedPieces firstReceipt
+          + minkowskiGeneratedPieces secondReceipt
+    , minkowskiGeneratedConvolutionEdges =
+        minkowskiGeneratedConvolutionEdges firstReceipt
+          + minkowskiGeneratedConvolutionEdges secondReceipt
+    , minkowskiOverlayPasses =
+        minkowskiOverlayPasses firstReceipt
+          + minkowskiOverlayPasses secondReceipt
+    , minkowskiExactCrossings =
+        minkowskiExactCrossings firstReceipt
+          + minkowskiExactCrossings secondReceipt
+    , minkowskiOutputCells = minkowskiOutputCells secondReceipt
+    , minkowskiExactCoordinateBitGrowth =
+        max
+          (minkowskiExactCoordinateBitGrowth firstReceipt)
+          (minkowskiExactCoordinateBitGrowth secondReceipt)
+    }
+
+coordinateBitGrowth :: [PlanarRegion] -> PlanarRegion -> Int
+coordinateBitGrowth inputs output =
+  max 0
+    ( regionCoordinateBits output
+        - foldl' (\maximumBits -> max maximumBits . regionCoordinateBits) 0 inputs
+    )
+
+regionCoordinateBits :: PlanarRegion -> Int
+regionCoordinateBits = foldl' componentBits 0 . planarRegionComponents
+ where
+  componentBits maximumBits component =
+    foldl'
+      loopBits
+      maximumBits
+      (polygonOuterLoop component : polygonHoleLoops component)
+  loopBits maximumBits =
+    foldl' pointBits maximumBits . exactLoopPoints
+  pointBits maximumBits point =
+    let (x, y) = exactPointCoordinates point
+     in max maximumBits (max (rationalBits x) (rationalBits y))
+
+rationalBits :: ExactRational -> Int
+rationalBits value =
+  max
+    (integerBitLength (abs (exactRationalNumerator value)))
+    (integerBitLength (exactRationalDenominator value))
diff --git a/src-build/Moonlight/Triangulation/Overlay.hs b/src-build/Moonlight/Triangulation/Overlay.hs
new file mode 100644
--- /dev/null
+++ b/src-build/Moonlight/Triangulation/Overlay.hs
@@ -0,0 +1,291 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | Exact labelled common refinement. Source boundaries descend through one
+-- exact segment-event plan, glue into canonical atomic constraints, and are
+-- admitted only when their binary64 DCEL realization preserves every exact
+-- relation.
+module Moonlight.Triangulation.Overlay
+  ( BoundaryLoopRef (..)
+  , OverlayOperand
+  , BoundaryVertexRef
+  , boundaryVertexComponent
+  , boundaryVertexLoop
+  , boundaryVertexLocalIndex
+  , BoundaryEdgeRef
+  , boundaryEdgeComponent
+  , boundaryEdgeLoop
+  , boundaryEdgeLocalIndex
+  , OverlayVertexOrigin
+  , overlayOriginLeftVertices
+  , overlayOriginRightVertices
+  , overlayOriginLeftEdges
+  , overlayOriginRightEdges
+  , OverlayEdgeOrigin
+  , overlayEdgeLeftSources
+  , overlayEdgeRightSources
+  , OverlaySupport
+  , overlaySupportLabels
+  , OverlayCellSupport (..)
+  , OverlayCellId (..)
+  , OverlayCellGeometry (..)
+  , OverlayCell (..)
+  , OverlayFace (..)
+  , OverlayVertex (..)
+  , OverlayEdge (..)
+  , OverlayReceipt (..)
+  , OverlayArrangementObstruction (..)
+  , OverlayCellWitness (..)
+  , OverlayError (..)
+  , OverlayResult
+  , OverlaySelectionKind (..)
+  , OverlaySelectionError (..)
+  , overlayLayers
+  , overlayEmbeddedTriangulation
+  , overlayReceipt
+  , overlayCells
+  , overlayArrangementVertices
+  , overlayArrangementEdges
+  , overlayPlanarLayer
+  , overlaySelectedRegion
+  , overlayClosedUnion
+  , overlayClosedIntersection
+  , overlayRegularizedDifference
+  ) where
+
+import Data.Bifunctor (first)
+import Control.Monad (filterM)
+import qualified Data.Map.Strict as Map
+import qualified Data.Vector as V
+import Moonlight.Triangulation.CellSet (ExactCellSet)
+import qualified Moonlight.Triangulation.Dcel as Dcel
+import Moonlight.Triangulation.Dcel (vertexData)
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( UndirectedEdgeId
+  , VertexId
+  )
+import Moonlight.Triangulation.Handles.Iterators.FixedIterators
+  ( innerFaces
+  , undirectedEdges
+  , vertices
+  )
+import Moonlight.Triangulation.Internal.CellSet (closeExactCellSetWith)
+import Moonlight.Triangulation.Internal.Overlay.Arrangement
+  ( certifyArrangement
+  )
+import Moonlight.Triangulation.Internal.Overlay.Resident
+  ( OverlayDiagonalSchedule (CanonicalOverlayDiagonals)
+  , edgeSupport
+  , faceLabels
+  , regionFaceLabels
+  , residentOverlay
+  , vertexSupport
+  )
+import Moonlight.Triangulation.Internal.Overlay.Types
+import Moonlight.Triangulation.Internal.Representation (Triangulation)
+import Moonlight.Triangulation.Internal.Types
+  ( ConstraintMode (Constrained)
+  )
+import Moonlight.Triangulation.Region
+  ( PlanarLayer
+  , PlanarRegion
+  , RegionPublicationError (..)
+  , emptyPlanarRegion
+  , planarLayerOutsideLabel
+  , planarLayerRegions
+  )
+import Moonlight.Triangulation.Internal.Region.Publication
+  ( labelledPlanarLayerFromExactCoordinates
+  , planarLayerFromAdmittedComponents
+  )
+
+-- | Construct the exact common refinement and its one faithful resident DCEL.
+overlayLayers
+  :: (Ord leftLabel, Ord rightLabel)
+  => PlanarLayer leftLabel
+  -> PlanarLayer rightLabel
+  -> Either
+      (OverlayError leftLabel rightLabel)
+      (OverlayResult leftLabel rightLabel)
+overlayLayers leftLayer rightLayer = do
+  certified <- certifyArrangement leftLayer rightLayer
+  residentOverlay
+    CanonicalOverlayDiagonals
+    (planarLayerOutsideLabel leftLayer, planarLayerOutsideLabel rightLayer)
+    certified
+
+-- | The binary64 realization used by existing DCEL observations. Exact overlay
+-- operations accept 'OverlayResult', never this projection.
+overlayEmbeddedTriangulation
+  :: OverlayResult leftLabel rightLabel
+  -> Triangulation
+      'Constrained
+      OverlayVertex
+      ()
+      OverlayEdge
+      OverlayFace
+overlayEmbeddedTriangulation = overlayResultTriangulation
+
+overlayReceipt :: OverlayResult leftLabel rightLabel -> OverlayReceipt
+overlayReceipt = overlayResultReceipt
+
+overlayCells
+  :: OverlayResult leftLabel rightLabel
+  -> [(OverlayCellId, OverlayCell leftLabel rightLabel)]
+overlayCells result =
+  V.toList
+    (V.imap (\index cell -> (OverlayCellId index, cell)) (overlayResultCells result))
+
+overlayArrangementVertices
+  :: OverlayResult leftLabel rightLabel
+  -> [(VertexId, OverlayVertex)]
+overlayArrangementVertices result =
+  let triangulation = overlayResultTriangulation result
+   in [(vertex, vertexData triangulation vertex) | vertex <- vertices triangulation]
+
+overlayArrangementEdges
+  :: OverlayResult leftLabel rightLabel
+  -> [(UndirectedEdgeId, OverlayEdgeOrigin)]
+overlayArrangementEdges result =
+  let triangulation = overlayResultTriangulation result
+   in [ (edge, origin)
+      | edge <- undirectedEdges triangulation
+      , OverlayBoundary origin <- [Dcel.undirectedEdgeData triangulation edge]
+      ]
+
+-- | Publish the already-admitted bounded cell geometry. The resident DCEL is
+-- a realization of these exact cells, not a second authoring source.
+overlayPlanarLayer
+  :: (Ord leftLabel, Ord rightLabel)
+  => OverlayResult leftLabel rightLabel
+  -> PlanarLayer (leftLabel, rightLabel)
+overlayPlanarLayer result =
+  planarLayerFromAdmittedComponents
+    (overlayResultOutsideLabels result)
+    [ ( (overlayCellLeft cell, overlayCellRight cell)
+      , component
+      )
+    | cell <- V.toList (overlayResultCells result)
+    , (overlayCellLeft cell, overlayCellRight cell)
+        /= overlayResultOutsideLabels result
+    , BoundedOverlayCell component <- [overlayCellGeometry cell]
+    ]
+
+-- | Publish the selected two-dimensional cells. Internal arrangement edges
+-- between differently labelled but jointly selected cells dissolve because
+-- selection precedes component descent.
+overlaySelectedRegion
+  :: ((leftLabel, rightLabel) -> Bool)
+  -> OverlayResult leftLabel rightLabel
+  -> Either RegionPublicationError PlanarRegion
+overlaySelectedRegion selected result
+  | selected (overlayResultOutsideLabels result) = Left RegionUnboundedSelection
+  | otherwise = do
+      published <-
+        labelledPlanarLayerFromExactCoordinates
+          False
+          triangulation
+          exactPointAt
+          labelFace
+      pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions published))
+ where
+  triangulation = overlayResultTriangulation result
+  exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))
+  labelFace face =
+    selected <$> regionFaceLabels result face
+
+overlayClosedUnion
+  :: (Ord leftLabel, Ord rightLabel)
+  => (leftLabel -> Bool)
+  -> (rightLabel -> Bool)
+  -> OverlayResult leftLabel rightLabel
+  -> Either OverlaySelectionError ExactCellSet
+overlayClosedUnion selectLeft selectRight =
+  selectClosedCells
+    ClosedUnionSelection
+    (\support -> supportAny selectLeft (overlaySupportLeft support) || supportAny selectRight (overlaySupportRight support))
+    (\leftLabel rightLabel -> selectLeft leftLabel || selectRight rightLabel)
+
+overlayClosedIntersection
+  :: (Ord leftLabel, Ord rightLabel)
+  => (leftLabel -> Bool)
+  -> (rightLabel -> Bool)
+  -> OverlayResult leftLabel rightLabel
+  -> Either OverlaySelectionError ExactCellSet
+overlayClosedIntersection selectLeft selectRight =
+  selectClosedCells
+    ClosedIntersectionSelection
+    (\support -> supportAny selectLeft (overlaySupportLeft support) && supportAny selectRight (overlaySupportRight support))
+    (\leftLabel rightLabel -> selectLeft leftLabel && selectRight rightLabel)
+
+overlayRegularizedDifference
+  :: (leftLabel -> Bool)
+  -> (rightLabel -> Bool)
+  -> OverlayResult leftLabel rightLabel
+  -> Either OverlaySelectionError ExactCellSet
+overlayRegularizedDifference selectLeft selectRight result =
+  let outsidePair = overlayResultOutsideLabels result
+      selectFace leftLabel rightLabel = selectLeft leftLabel && not (selectRight rightLabel)
+   in if uncurry selectFace outsidePair
+        then Left (OverlaySelectionContainsUnboundedCell RegularizedDifferenceSelection)
+        else closeSelectedCells [] [] selectFace result
+
+selectClosedCells
+  :: (Ord leftLabel, Ord rightLabel)
+  => OverlaySelectionKind
+  -> (OverlayCellSupport leftLabel rightLabel -> Bool)
+  -> (leftLabel -> rightLabel -> Bool)
+  -> OverlayResult leftLabel rightLabel
+  -> Either OverlaySelectionError ExactCellSet
+selectClosedCells selectionKind selectSupport selectFace result =
+  let outsidePair = overlayResultOutsideLabels result
+      triangulation = overlayResultTriangulation result
+   in if uncurry selectFace outsidePair
+        then Left (OverlaySelectionContainsUnboundedCell selectionKind)
+        else do
+          selectedVertices <-
+            filterM
+              ( fmap selectSupport
+                  . first OverlaySelectionProvenance
+                  . vertexSupport result
+              )
+              (vertices triangulation)
+          selectedEdges <-
+            filterM
+              (\edge ->
+                 case Dcel.undirectedEdgeData triangulation edge of
+                   OverlayDiagonal -> Right False
+                   OverlayBoundary _ ->
+                     selectSupport
+                       <$> first OverlaySelectionProvenance (edgeSupport result edge))
+              (undirectedEdges triangulation)
+          closeSelectedCells selectedVertices selectedEdges selectFace result
+
+closeSelectedCells
+  :: [VertexId]
+  -> [UndirectedEdgeId]
+  -> (leftLabel -> rightLabel -> Bool)
+  -> OverlayResult leftLabel rightLabel
+  -> Either OverlaySelectionError ExactCellSet
+closeSelectedCells selectedVertices selectedEdges selectFace result =
+  let triangulation = overlayResultTriangulation result
+      exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))
+   in do
+        selectedFaces <-
+          filterM
+            ( fmap (uncurry selectFace)
+                . first OverlaySelectionProvenance
+                . faceLabels result
+            )
+            (innerFaces triangulation)
+        first OverlaySelectionInvalid
+          ( closeExactCellSetWith
+              exactPointAt
+              triangulation
+              selectedVertices
+              selectedEdges
+              selectedFaces
+          )
+
+supportAny :: (label -> Bool) -> OverlaySupport label -> Bool
+supportAny predicate = any predicate . overlaySupportLabels
diff --git a/src-core/Moonlight/Triangulation/Internal/Dyadic.hs b/src-core/Moonlight/Triangulation/Internal/Dyadic.hs
--- a/src-core/Moonlight/Triangulation/Internal/Dyadic.hs
+++ b/src-core/Moonlight/Triangulation/Internal/Dyadic.hs
@@ -7,12 +7,21 @@
   ( exactOrientDet
   , exactOrientSignDouble
   , exactInCircleDet
+  , exactCircumradiusSquaredWithin
   , exactBarycentricDeterminants
   , exactDiametralDot
   , integerRatioToDouble
+  , integerBitLength
   ) where
 
-import Data.Bits (shiftL, shiftR)
+import Data.Bits
+  ( countLeadingZeros
+  , countTrailingZeros
+  , finiteBitSize
+  , shiftL
+  , shiftR
+  )
+import Data.Word (Word64)
 #if WORD_SIZE_IN_BITS == 64
 import GHC.Exts
   ( Double (D#)
@@ -66,7 +75,7 @@
 
 aligned6
   :: Double -> Double -> Double -> Double -> Double -> Double
-  -> (Integer, Integer, Integer, Integer, Integer, Integer)
+  -> (Int, Integer, Integer, Integer, Integer, Integer, Integer)
 aligned6 a b c d e f =
   let !da = decodeFloat a
       !db = decodeFloat b
@@ -75,7 +84,8 @@
       !de = decodeFloat e
       !df = decodeFloat f
       !power = commonExponent [da, db, dc, dd, de, df]
-   in ( alignDecoded power da
+   in ( power
+      , alignDecoded power da
       , alignDecoded power db
       , alignDecoded power dc
       , alignDecoded power dd
@@ -109,13 +119,164 @@
 exactOrientDet
   :: Double -> Double -> Double -> Double -> Double -> Double -> Integer
 exactOrientDet ax ay bx by cx cy =
-  let (!iax, !iay, !ibx, !iby, !icx, !icy) = aligned6 ax ay bx by cx cy
+  let (!_, !iax, !iay, !ibx, !iby, !icx, !icy) = aligned6 ax ay bx by cx cy
       !acx = iax - icx
       !acy = iay - icy
       !bcx = ibx - icx
       !bcy = iby - icy
    in acx * bcy - acy * bcx
 
+-- | Exact closed comparison of squared circumradius with a finite,
+-- non-negative binary64 threshold. No constructed circumcenter participates.
+exactCircumradiusSquaredWithin
+  :: Double
+  -> Double -> Double -> Double -> Double -> Double -> Double
+  -> Bool
+exactCircumradiusSquaredWithin
+  threshold
+  ax ay bx by cx cy =
+    let (!thresholdMantissa, !thresholdPower) = decodeFloat threshold
+     in smallIntegralCircumradiusSquaredWithin
+          (fromInteger thresholdMantissa)
+          thresholdPower
+          ax ay bx by cx cy
+{-# INLINE exactCircumradiusSquaredWithin #-}
+
+arbitraryCircumradiusSquaredWithin
+  :: Integer
+  -> Int
+  -> Double -> Double -> Double -> Double -> Double -> Double
+  -> Bool
+arbitraryCircumradiusSquaredWithin
+  thresholdMantissa
+  thresholdPower
+  ax ay bx by cx cy =
+  let (!coordinatePower, !iax, !iay, !ibx, !iby, !icx, !icy) =
+        aligned6 ax ay bx by cx cy
+      !abx = ibx - iax
+      !aby = iby - iay
+      !acx = icx - iax
+      !acy = icy - iay
+      !bcx = icx - ibx
+      !bcy = icy - iby
+      !abSquared = abx * abx + aby * aby
+      !acSquared = acx * acx + acy * acy
+      !bcSquared = bcx * bcx + bcy * bcy
+      !determinant = abx * acy - aby * acx
+      !radiusNumerator = abSquared * acSquared * bcSquared
+      !thresholdDenominator =
+        4 * determinant * determinant * thresholdMantissa
+   in determinant /= 0
+        && compareDyadic
+          radiusNumerator
+          (6 * coordinatePower)
+          thresholdDenominator
+          (4 * coordinatePower + thresholdPower)
+          /= GT
+
+-- Integer-sized edges cover grid, pixel, and indexed-world faces without
+-- allocating arbitrary-precision mantissas. Larger or fractional edges
+-- descend to the general dyadic comparison above.
+smallIntegralCircumradiusSquaredWithin
+  :: Word64
+  -> Int
+  -> Double -> Double -> Double -> Double -> Double -> Double
+  -> Bool
+smallIntegralCircumradiusSquaredWithin
+  rawThresholdMantissa
+  rawThresholdPower
+  ax ay bx by cx cy =
+    if not admittedDifferences
+      then
+        arbitraryCircumradiusSquaredWithin
+          (toInteger rawThresholdMantissa)
+          rawThresholdPower
+          ax ay bx by cx cy
+      else
+        let !abSquared = squaredIntegralLength abx aby
+            !acSquared = squaredIntegralLength acx acy
+            !bcSquared = squaredIntegralLength bcx bcy
+            !radiusNumerator = abSquared * acSquared * bcSquared
+            !determinant = abx * acy - aby * acx
+            !determinantMagnitude = fromIntegral (abs determinant)
+         in if determinant == 0 || rawThresholdMantissa == 0
+              then False
+              else
+                let !trailingZeros = countTrailingZeros rawThresholdMantissa
+                    !thresholdMantissa = rawThresholdMantissa `shiftR` trailingZeros
+                    !thresholdPower = rawThresholdPower + trailingZeros
+                    !determinantSquared = determinantMagnitude * determinantMagnitude
+                    !scaledDeterminant = 4 * determinantSquared
+                    !productsFit =
+                      determinantMagnitude <= maxBound `quot` determinantMagnitude
+                        && determinantSquared <= maxBound `quot` 4
+                        && thresholdMantissa <= maxBound `quot` scaledDeterminant
+                 in if productsFit
+                      then
+                        compareWordDyadic
+                          radiusNumerator
+                          0
+                          (scaledDeterminant * thresholdMantissa)
+                          thresholdPower
+                          /= GT
+                      else
+                        arbitraryCircumradiusSquaredWithin
+                          (toInteger rawThresholdMantissa)
+                          rawThresholdPower
+                          ax ay bx by cx cy
+ where
+  !abxValue = bx - ax
+  !abyValue = by - ay
+  !acxValue = cx - ax
+  !acyValue = cy - ay
+  !abx = truncate abxValue
+  !aby = truncate abyValue
+  !acx = truncate acxValue
+  !acy = truncate acyValue
+  !bcx = acx - abx
+  !bcy = acy - aby
+  admittedDifferences =
+    integralDifference abxValue abx
+      && integralDifference abyValue aby
+      && integralDifference acxValue acx
+      && integralDifference acyValue acy
+{-# INLINE smallIntegralCircumradiusSquaredWithin #-}
+
+integralDifference :: Double -> Int -> Bool
+integralDifference value integral =
+  abs value <= 512 && fromIntegral integral == value
+{-# INLINE integralDifference #-}
+
+squaredIntegralLength :: Int -> Int -> Word64
+squaredIntegralLength x y =
+  let !xMagnitude = fromIntegral (abs x)
+      !yMagnitude = fromIntegral (abs y)
+   in xMagnitude * xMagnitude + yMagnitude * yMagnitude
+{-# INLINE squaredIntegralLength #-}
+
+compareWordDyadic :: Word64 -> Int -> Word64 -> Int -> Ordering
+compareWordDyadic left leftPower right rightPower
+  | left == 0 = compare left right
+  | right == 0 = GT
+  | leftMagnitude /= rightMagnitude = compare leftMagnitude rightMagnitude
+  | leftPower < rightPower = compare left (right `shiftL` (rightPower - leftPower))
+  | otherwise = compare (left `shiftL` (leftPower - rightPower)) right
+ where
+  !leftMagnitude = wordBitLength left + leftPower
+  !rightMagnitude = wordBitLength right + rightPower
+
+  wordBitLength :: Word64 -> Int
+  wordBitLength value = finiteBitSize value - countLeadingZeros value
+{-# INLINE compareWordDyadic #-}
+
+compareDyadic :: Integer -> Int -> Integer -> Int -> Ordering
+compareDyadic left leftPower right rightPower =
+  case compare leftPower rightPower of
+    LT -> compare left (right `shiftL` (rightPower - leftPower))
+    EQ -> compare left right
+    GT -> compare (left `shiftL` (leftPower - rightPower)) right
+{-# INLINE compareDyadic #-}
+
 exactInCircleDet
   :: Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double
   -> Integer
@@ -139,7 +300,7 @@
 exactDiametralDot
   :: Double -> Double -> Double -> Double -> Double -> Double -> Integer
 exactDiametralDot ax ay bx by px py =
-  let (!iax, !iay, !ibx, !iby, !ipx, !ipy) = aligned6 ax ay bx by px py
+  let (!_, !iax, !iay, !ibx, !iby, !ipx, !ipy) = aligned6 ax ay bx by px py
       !pax = ipx - iax
       !pay = ipy - iay
       !pbx = ipx - ibx
diff --git a/src-core/Moonlight/Triangulation/Internal/ExactRational.hs b/src-core/Moonlight/Triangulation/Internal/ExactRational.hs
new file mode 100644
--- /dev/null
+++ b/src-core/Moonlight/Triangulation/Internal/ExactRational.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+-- | Normalized exact rational arithmetic without geometric dependencies.
+module Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational
+  , ExactArithmeticError (..)
+  , exactRational
+  , exactRationalFromDouble
+  , exactRationalFromFiniteDouble
+  , exactRationalFromDyadic
+  , exactRationalNumerator
+  , exactRationalDenominator
+  , exactRationalIsZero
+  , exactDivide
+  , exactSignum
+  ) where
+
+import Control.DeepSeq (NFData)
+import Data.Bits (shiftL)
+import Data.Ratio (Ratio, (%))
+import qualified Data.Ratio as Ratio
+import GHC.Generics (Generic)
+
+-- | A checked wrapper around a reduced ratio with a strictly positive
+-- denominator. 'Data.Ratio' owns normalization, including the unique zero
+-- representation @0 / 1@.
+newtype ExactRational = ExactRational (Ratio Integer)
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving newtype (Num)
+  deriving anyclass (NFData)
+
+-- | Typed refusals from exact rational construction and division.
+data ExactArithmeticError
+  = ExactZeroDenominator
+  | ExactZeroDivisor
+  | ExactNaNInput
+  | ExactInfiniteInput
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Construct a reduced rational, moving any sign onto the numerator and
+-- refusing a zero denominator.
+exactRational :: Integer -> Integer -> Either ExactArithmeticError ExactRational
+exactRational _ 0 = Left ExactZeroDenominator
+exactRational numerator denominator = Right (ExactRational (numerator % denominator))
+
+-- | Convert a binary64 value without loss, refusing NaN and infinities.
+exactRationalFromDouble :: Double -> Either ExactArithmeticError ExactRational
+exactRationalFromDouble value
+  | isNaN value = Left ExactNaNInput
+  | isInfinite value = Left ExactInfiniteInput
+  | otherwise = Right (exactRationalFromFiniteDouble value)
+
+-- | Convert an already-admitted finite coordinate without loss. The caller
+-- supplies a finite value, such as a coordinate inside a validated
+-- @QueryPoint@, so this worker does not repeat the admission refusal.
+exactRationalFromFiniteDouble :: Double -> ExactRational
+exactRationalFromFiniteDouble value =
+  uncurry exactRationalFromDyadic (decodeFloat value)
+{-# INLINE exactRationalFromFiniteDouble #-}
+
+-- | Construct @numerator * 2^power@ without a partial denominator path.
+exactRationalFromDyadic :: Integer -> Int -> ExactRational
+exactRationalFromDyadic numerator power
+  | power >= 0 = fromInteger (numerator `shiftL` power)
+  | otherwise = ExactRational (numerator % (1 `shiftL` negate power))
+{-# INLINE exactRationalFromDyadic #-}
+
+-- | Read the reduced numerator.
+exactRationalNumerator :: ExactRational -> Integer
+exactRationalNumerator (ExactRational value) = Ratio.numerator value
+{-# INLINE exactRationalNumerator #-}
+
+-- | Read the strictly positive reduced denominator.
+exactRationalDenominator :: ExactRational -> Integer
+exactRationalDenominator (ExactRational value) = Ratio.denominator value
+{-# INLINE exactRationalDenominator #-}
+
+-- | Test whether the exact value is zero.
+exactRationalIsZero :: ExactRational -> Bool
+exactRationalIsZero (ExactRational value) = Ratio.numerator value == 0
+{-# INLINE exactRationalIsZero #-}
+
+-- | Divide by a nonzero exact rational, refusing a zero divisor explicitly.
+exactDivide
+  :: ExactRational
+  -> ExactRational
+  -> Either ExactArithmeticError ExactRational
+exactDivide (ExactRational left) (ExactRational right)
+  | Ratio.numerator right == 0 = Left ExactZeroDivisor
+  | otherwise = Right (ExactRational (left / right))
+
+-- | Compare an exact rational with zero through its canonical numerator.
+exactSignum :: ExactRational -> Ordering
+exactSignum (ExactRational value) = compare (Ratio.numerator value) 0
+{-# INLINE exactSignum #-}
diff --git a/src-core/Moonlight/Triangulation/Internal/Paged.hs b/src-core/Moonlight/Triangulation/Internal/Paged.hs
--- a/src-core/Moonlight/Triangulation/Internal/Paged.hs
+++ b/src-core/Moonlight/Triangulation/Internal/Paged.hs
@@ -3,6 +3,7 @@
 module Moonlight.Triangulation.Internal.Paged
   ( Paged
   , MutablePaged
+  , FlatMutablePaged
   , emptyPaged
   , fromVector
   , fromLocalVector
@@ -16,6 +17,9 @@
   , thawPaged
   , thawPagedDense
   , thawPagedShaped
+  , flatMutableSection
+  , readFlatMutable
+  , writeFlatMutable
   , readPaged
   , writePaged
   , freezePaged
@@ -52,6 +56,12 @@
       !(MUV.MVector s Bool)
       !(STRef s Int)
 
+-- | A proof that one mutable sequence is in its contiguous physical section.
+-- The constructor stays private: callers can only obtain it by descending
+-- through 'flatMutableSection', after which hot indexed programs no longer
+-- re-test the storage sum at every cell.
+newtype FlatMutablePaged s a = FlatMutablePaged (MUV.MVector s a)
+
 localPageBits :: Int
 localPageBits = 8
 {-# INLINE localPageBits #-}
@@ -289,6 +299,21 @@
                 copyPage (pageIndex + 1) (offset + width)
        in copyPage 0 0
   pure (MutableFlatPaged (pagedBits paged) values)
+
+flatMutableSection :: MutablePaged s a -> Maybe (FlatMutablePaged s a)
+flatMutableSection mutable =
+  case mutable of
+    MutableFlatPaged _ values -> Just (FlatMutablePaged values)
+    MutableSharedPaged{} -> Nothing
+{-# INLINE flatMutableSection #-}
+
+readFlatMutable :: U.Unbox a => FlatMutablePaged s a -> Int -> ST s a
+readFlatMutable (FlatMutablePaged values) = MUV.unsafeRead values
+{-# INLINE readFlatMutable #-}
+
+writeFlatMutable :: U.Unbox a => FlatMutablePaged s a -> Int -> a -> ST s ()
+writeFlatMutable (FlatMutablePaged values) = MUV.unsafeWrite values
+{-# INLINE writeFlatMutable #-}
 
 readPaged :: U.Unbox a => MutablePaged s a -> Int -> ST s a
 readPaged mutable index =
diff --git a/src-core/Moonlight/Triangulation/Scalar.hs b/src-core/Moonlight/Triangulation/Scalar.hs
--- a/src-core/Moonlight/Triangulation/Scalar.hs
+++ b/src-core/Moonlight/Triangulation/Scalar.hs
@@ -1,3 +1,5 @@
+{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}
+
 -- | The binary64 coordinate kernel and exact predicate boundary.
 module Moonlight.Triangulation.Scalar
   ( scalarName
@@ -9,16 +11,22 @@
   , scalarInCircleErrorBound
   , orient2dCoordinates
   , inCircleCoordinates
+  , circumradiusSquaredWithinCoordinates
   , BinaryFormat
   , formatRadix
   , formatMantissaDigits
   , formatExponentRange
   , minimumAllowedCoordinate
   , maximumAllowedCoordinate
+  , isFinite
   , canonicalScalarZero
   ) where
 
-import Moonlight.Triangulation.Internal.Dyadic (exactInCircleDet, exactOrientSignDouble)
+import Moonlight.Triangulation.Internal.Dyadic
+  ( exactCircumradiusSquaredWithin
+  , exactInCircleDet
+  , exactOrientSignDouble
+  )
 
 -- | The coordinate component of canonical point identity. IEEE signed zeros
 -- compare equal but hash differently by bits; every coordinate-keyed owner
@@ -85,6 +93,44 @@
   -> Double -> Double -> Double -> Double
   -> Ordering
 inCircleCoordinates = filteredInCircle scalarInCircleErrorBound
+
+-- | Closed exact circumradius membership at a finite, non-negative binary64
+-- threshold. Invalid thresholds and collinear triples are outside.
+circumradiusSquaredWithinCoordinates
+  :: Double
+  -> Double -> Double -> Double -> Double -> Double -> Double
+  -> Bool
+circumradiusSquaredWithinCoordinates threshold ax ay bx by cx cy
+    | threshold < 0 || not (isFinite threshold) = False
+    | not (isFinite ax && isFinite ay && isFinite bx
+        && isFinite by && isFinite cx && isFinite cy) = False
+    | isFinite difference && isFinite tolerance
+        && tolerance > 0 && abs difference > tolerance = difference <= 0
+    | otherwise = exactCircumradiusSquaredWithin threshold ax ay bx by cx cy
+   where
+    !abx = bx - ax
+    !aby = by - ay
+    !acx = cx - ax
+    !acy = cy - ay
+    !bcx = cx - bx
+    !bcy = cy - by
+    !abSquared = abx * abx + aby * aby
+    !acSquared = acx * acx + acy * acy
+    !bcSquared = bcx * bcx + bcy * bcy
+    !determinant = abx * acy - aby * acx
+    !radiusNumerator = abSquared * acSquared * bcSquared
+    !thresholdDenominator = 4 * determinant * determinant * threshold
+    !difference = radiusNumerator - thresholdDenominator
+    !permanent = abs radiusNumerator + abs thresholdDenominator
+    !tolerance = 128 * scalarUnitRoundoff * permanent
+
+{-# INLINE circumradiusSquaredWithinCoordinates #-}
+
+-- | Whether a scalar is neither infinite nor NaN. Pure subtraction avoids the
+-- FFI calls used by base's predicates in the supported GHC.
+isFinite :: Double -> Bool
+isFinite value = value - value == 0
+{-# INLINE isFinite #-}
 
 -- The binary64 kernel pairs the approximation test with the
 -- fixed-precision exact sign, which answers the dyadic determinant's sign in
diff --git a/src-dcel/Moonlight/Triangulation/CellSet.hs b/src-dcel/Moonlight/Triangulation/CellSet.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/CellSet.hs
@@ -0,0 +1,18 @@
+-- | Finite downward-closed selections of relatively open vertices, edges, and
+-- bounded faces. A selection retains its resident DCEL and exact coordinate
+-- projection behind an opaque carrier.
+module Moonlight.Triangulation.CellSet
+  ( ExactCellSet
+  , CellSelectionError (..)
+  , exactCellSet
+  , closeFaceCellSet
+  , exactCellSetVertexCount
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , foldExactCellVertices
+  , foldExactCellEdges
+  , foldExactCellFaces
+  ) where
+
+import Moonlight.Triangulation.Internal.CellSet
+
diff --git a/src-dcel/Moonlight/Triangulation/Dcel.hs b/src-dcel/Moonlight/Triangulation/Dcel.hs
--- a/src-dcel/Moonlight/Triangulation/Dcel.hs
+++ b/src-dcel/Moonlight/Triangulation/Dcel.hs
@@ -24,6 +24,8 @@
   , mapDirectedEdges
   , mapUndirectedEdges
   , mapFaces
+  , imapUndirectedEdges
+  , imapFaces
   , vertexOutEdge
   , adjacentEdge
   , origin
@@ -39,6 +41,7 @@
   , faceDirectedEdges
   , faceVertices
   , innerFaceDirectedEdges
+  , innerFaceDirectedEdgeTriples
   , innerFaceVertices
   , innerFaceVertexTriples
   , vertexOutgoingEdges
@@ -249,8 +252,8 @@
 
 -- | The three directed edges of a bounded triangular face.
 innerFaceDirectedEdges :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe (DirectedEdgeId, DirectedEdgeId, DirectedEdgeId)
-innerFaceDirectedEdges triangulation face
-  | face == outerFace = Nothing
+innerFaceDirectedEdges triangulation face@(FaceId rawFace)
+  | face == outerFace || fromIntegral rawFace >= numFaces triangulation = Nothing
   | otherwise = do
       e0 <- adjacentEdge triangulation face
       let !e1 = next triangulation e0
@@ -260,6 +263,20 @@
         else Nothing
 {-# INLINE innerFaceDirectedEdges #-}
 
+-- | Dense bounded-face directed-edge triples in face-handle order.
+innerFaceDirectedEdgeTriples
+  :: Triangulation mode vertex directed undirected face
+  -> V.Vector (DirectedEdgeId, DirectedEdgeId, DirectedEdgeId)
+innerFaceDirectedEdgeTriples triangulation =
+  V.generate (numInnerFaces triangulation) $ \innerFaceIndex ->
+    let !faceIndex = innerFaceIndex + 1
+        !firstEdge =
+          DirectedEdgeId
+            (pagedUnsafeIndex (triFaceEdge triangulation) faceIndex)
+        !secondEdge = next triangulation firstEdge
+     in (firstEdge, secondEdge, next triangulation secondEdge)
+{-# INLINE innerFaceDirectedEdgeTriples #-}
+
 -- | The three vertices of a bounded triangular face.
 innerFaceVertices :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe (VertexId, VertexId, VertexId)
 innerFaceVertices triangulation face = do
@@ -272,17 +289,14 @@
   :: Triangulation mode vertex directed undirected face
   -> V.Vector (VertexId, VertexId, VertexId)
 innerFaceVertexTriples triangulation =
-  V.generate (numInnerFaces triangulation) $ \innerFaceIndex ->
-    let !faceIndex = innerFaceIndex + 1
-        !firstEdge =
-          DirectedEdgeId
-            (pagedUnsafeIndex (triFaceEdge triangulation) faceIndex)
-        !secondEdge = next triangulation firstEdge
-        !thirdEdge = next triangulation secondEdge
-     in ( origin triangulation firstEdge
-        , origin triangulation secondEdge
-        , origin triangulation thirdEdge
-        )
+  fmap
+    (\(firstEdge, secondEdge, thirdEdge) ->
+      ( origin triangulation firstEdge
+      , origin triangulation secondEdge
+      , origin triangulation thirdEdge
+      )
+    )
+    (innerFaceDirectedEdgeTriples triangulation)
 {-# INLINE innerFaceVertexTriples #-}
 
 -- | Counter-clockwise ring of directed edges originating at a vertex.
diff --git a/src-dcel/Moonlight/Triangulation/Exact.hs b/src-dcel/Moonlight/Triangulation/Exact.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Exact.hs
@@ -0,0 +1,266 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | Exact rational planar geometry over admitted binary64 points.
+module Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , exactPoint
+  , exactPointCoordinates
+  , exactPointCross
+  , ExactVector (..)
+  , exactVectorFromPoints
+  , addExactVectors
+  , exactCross
+  , compareExactVectorAngle
+  , translateExactPoint
+  , ExactSegment
+  , ExactGeometryError (..)
+  , exactSegment
+  , exactSegmentEndpoints
+  , exactPointFromPoint
+  , exactPointFromQueryPoint
+  , exactPointToEmbeddingCandidate
+  , exactOrient2d
+  , exactOnClosedSegment
+  , SegmentRelation (..)
+  , allSegmentRelations
+  , exactSegmentRelation
+  , ExactIntersectionError (..)
+  , exactLineIntersection
+  , exactSupportingLineIntersection
+  ) where
+
+import Control.DeepSeq (NFData)
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Internal.Dyadic (integerRatioToDouble)
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactArithmeticError (..)
+  , ExactRational
+  , exactDivide
+  , exactRationalDenominator
+  , exactRationalFromFiniteDouble
+  , exactRationalNumerator
+  , exactSignum
+  )
+import Moonlight.Triangulation.Internal.SegmentRelation
+  ( SegmentRelation (..)
+  , allSegmentRelations
+  , segmentRelationWith
+  )
+import Moonlight.Triangulation.Math (mkQueryPoint)
+import Moonlight.Triangulation.Types
+  ( Point (..)
+  , PointValidationError
+  , QueryPoint
+  , queryPointValue
+  )
+
+-- | A strict exact Cartesian point.
+data ExactPoint = ExactPoint !ExactRational !ExactRational
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A strict exact segment whose endpoints are distinct.
+data ExactSegment = ExactSegment !ExactPoint !ExactPoint
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Witness-bearing refusals from exact segment construction.
+data ExactGeometryError
+  = ExactSegmentEndpointsCoincide !ExactPoint
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Witness-bearing refusals from exact line intersection.
+data ExactIntersectionError
+  = ExactIntersectionAbsent !SegmentRelation
+  | ExactIntersectionNonUnique !SegmentRelation
+  | ExactIntersectionParallelOrDegenerate !ExactRational
+  | ExactIntersectionArithmetic !ExactArithmeticError
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Construct an exact point from two exact coordinates.
+exactPoint :: ExactRational -> ExactRational -> ExactPoint
+exactPoint = ExactPoint
+{-# INLINE exactPoint #-}
+
+-- | Read both exact point coordinates.
+exactPointCoordinates :: ExactPoint -> (ExactRational, ExactRational)
+exactPointCoordinates (ExactPoint x y) = (x, y)
+{-# INLINE exactPointCoordinates #-}
+
+-- | Determinant of two points regarded as vectors from the Cartesian origin.
+exactPointCross :: ExactPoint -> ExactPoint -> ExactRational
+exactPointCross (ExactPoint ax ay) (ExactPoint bx by) = ax * by - ay * bx
+{-# INLINE exactPointCross #-}
+
+-- | Construct an exact segment, refusing coincident endpoints with their
+-- shared point as the witness.
+exactSegment
+  :: ExactPoint
+  -> ExactPoint
+  -> Either ExactGeometryError ExactSegment
+exactSegment from to
+  | from == to = Left (ExactSegmentEndpointsCoincide from)
+  | otherwise = Right (ExactSegment from to)
+
+-- | Read both distinct exact segment endpoints.
+exactSegmentEndpoints :: ExactSegment -> (ExactPoint, ExactPoint)
+exactSegmentEndpoints (ExactSegment from to) = (from, to)
+{-# INLINE exactSegmentEndpoints #-}
+
+-- | Validate and exactly embed a raw binary64 point.
+exactPointFromPoint :: Point -> Either PointValidationError ExactPoint
+exactPointFromPoint = fmap exactPointFromQueryPoint . mkQueryPoint
+
+-- | Exactly embed an already-admitted query point without repeating
+-- coordinate validation.
+exactPointFromQueryPoint :: QueryPoint -> ExactPoint
+exactPointFromQueryPoint queryPoint =
+  case queryPointValue queryPoint of
+    Point x y ->
+      ExactPoint
+        (exactRationalFromFiniteDouble x)
+        (exactRationalFromFiniteDouble y)
+{-# INLINE exactPointFromQueryPoint #-}
+
+-- | Deterministically project an exact point to a validated binary64
+-- embedding candidate. This is not a correctly-rounded nearest-double claim;
+-- callers must certify the candidate projection before relying on it.
+exactPointToEmbeddingCandidate
+  :: ExactPoint
+  -> Either PointValidationError Point
+exactPointToEmbeddingCandidate (ExactPoint x y) =
+  queryPointValue
+    <$> mkQueryPoint
+      ( Point
+          (integerRatioToDouble (exactRationalNumerator x) (exactRationalDenominator x))
+          (integerRatioToDouble (exactRationalNumerator y) (exactRationalDenominator y))
+      )
+
+-- | Exact orientation of an ordered triple. 'GT' is a positive determinant
+-- and counter-clockwise turn, 'EQ' is collinear, and 'LT' is clockwise.
+exactOrient2d :: ExactPoint -> ExactPoint -> ExactPoint -> Ordering
+exactOrient2d
+  (ExactPoint ax ay)
+  (ExactPoint bx by)
+  (ExactPoint cx cy) =
+    exactSignum
+      ((bx - ax) * (cy - ay) - (by - ay) * (cx - ax))
+{-# INLINE exactOrient2d #-}
+
+-- | Whether an exact point lies on an exact closed segment.
+exactOnClosedSegment :: ExactPoint -> ExactPoint -> ExactPoint -> Bool
+exactOnClosedSegment
+  from@(ExactPoint ax ay)
+  to@(ExactPoint bx by)
+  query@(ExactPoint qx qy) =
+    exactOrient2d from to query == EQ
+      && qx >= min ax bx
+      && qx <= max ax bx
+      && qy >= min ay by
+      && qy <= max ay by
+{-# INLINE exactOnClosedSegment #-}
+
+-- | Exact rational specialization of the one closed-segment relation policy.
+exactSegmentRelation
+  :: ExactPoint
+  -> ExactPoint
+  -> ExactPoint
+  -> ExactPoint
+  -> SegmentRelation
+exactSegmentRelation =
+  segmentRelationWith (==) compare exactOrient2d exactOnClosedSegment
+{-# INLINE exactSegmentRelation #-}
+
+-- | Return the unique exact intersection of two exact segments. Disjoint and
+-- non-unique relations are refused with their relation witness; a zero line
+-- cross product and arithmetic failure retain their exact witnesses.
+exactLineIntersection
+  :: ExactSegment
+  -> ExactSegment
+  -> Either ExactIntersectionError ExactPoint
+exactLineIntersection
+  (ExactSegment a b)
+  (ExactSegment c d) =
+    case exactSegmentRelation a b c d of
+      SegmentsDisjoint -> Left (ExactIntersectionAbsent SegmentsDisjoint)
+      SegmentsDuplicate -> Left (ExactIntersectionNonUnique SegmentsDuplicate)
+      SegmentsCollinearlyOverlap ->
+        Left (ExactIntersectionNonUnique SegmentsCollinearlyOverlap)
+      SegmentsShareEndpoint -> uniqueIntersection
+      SegmentsProperlyCross -> uniqueIntersection
+      SegmentEndpointTouchesInterior -> uniqueIntersection
+ where
+  uniqueIntersection =
+    exactSupportingLineIntersection (ExactSegment a b) (ExactSegment c d)
+
+-- | Intersect the infinite supporting lines of two admitted exact segments.
+-- Unlike 'exactLineIntersection', the intersection need not lie inside either
+-- closed segment. Parallel supporting lines retain the exact zero denominator
+-- witness.
+exactSupportingLineIntersection
+  :: ExactSegment
+  -> ExactSegment
+  -> Either ExactIntersectionError ExactPoint
+exactSupportingLineIntersection
+  (ExactSegment a b)
+  (ExactSegment c d) =
+  let directionAB = exactVectorFromPoints a b
+      directionCD = exactVectorFromPoints c d
+      fromAToC = exactVectorFromPoints a c
+      denominator = exactCross directionAB directionCD
+      numerator = exactCross fromAToC directionCD
+   in case exactDivide numerator denominator of
+        Left ExactZeroDivisor ->
+          Left (ExactIntersectionParallelOrDegenerate denominator)
+        Left arithmeticError -> Left (ExactIntersectionArithmetic arithmeticError)
+        Right parameter ->
+          Right (translateExactPoint a (scaleExactVector parameter directionAB))
+
+-- | A strict exact displacement vector. Points and vectors remain distinct;
+-- all exact planar algorithms share this single vector carrier.
+data ExactVector = ExactVector !ExactRational !ExactRational
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+exactVectorFromPoints :: ExactPoint -> ExactPoint -> ExactVector
+exactVectorFromPoints (ExactPoint ax ay) (ExactPoint bx by) =
+  ExactVector (bx - ax) (by - ay)
+
+addExactVectors :: ExactVector -> ExactVector -> ExactVector
+addExactVectors (ExactVector ax ay) (ExactVector bx by) =
+  ExactVector (ax + bx) (ay + by)
+
+exactCross :: ExactVector -> ExactVector -> ExactRational
+exactCross (ExactVector ax ay) (ExactVector bx by) =
+  ax * by - ay * bx
+
+-- | Counter-clockwise angular order from the positive x-axis. Collinear
+-- vectors on the same ray compare equal so convolution can merge them;
+-- callers that need a total point order may add their own radial tie-break.
+compareExactVectorAngle :: ExactVector -> ExactVector -> Ordering
+compareExactVectorAngle left right =
+  case compare (vectorHalf left) (vectorHalf right) of
+    EQ ->
+      case exactSignum (exactCross left right) of
+        GT -> LT
+        LT -> GT
+        EQ -> EQ
+    ordering -> ordering
+ where
+  vectorHalf (ExactVector x y)
+    | exactSignum y == GT = False
+    | exactSignum y == EQ && exactSignum x /= LT = False
+    | otherwise = True
+{-# INLINE compareExactVectorAngle #-}
+
+scaleExactVector :: ExactRational -> ExactVector -> ExactVector
+scaleExactVector scale (ExactVector x y) =
+  ExactVector (scale * x) (scale * y)
+
+translateExactPoint :: ExactPoint -> ExactVector -> ExactPoint
+translateExactPoint (ExactPoint x y) (ExactVector dx dy) =
+  ExactPoint (x + dx) (y + dy)
diff --git a/src-dcel/Moonlight/Triangulation/FloodFillIterator.hs b/src-dcel/Moonlight/Triangulation/FloodFillIterator.hs
--- a/src-dcel/Moonlight/Triangulation/FloodFillIterator.hs
+++ b/src-dcel/Moonlight/Triangulation/FloodFillIterator.hs
@@ -1,3 +1,7 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE FlexibleInstances #-}
 
 -- | Shape queries and face flood fills over immutable triangulations.
@@ -5,6 +9,7 @@
   ( DistanceMetric (..)
   , CircleMetric
   , CircleMetricError (..)
+  , RadiusSquaredError (..)
   , RectangleMetric
   , RectangleMetricError (..)
   , circleMetric
@@ -18,16 +23,96 @@
   , floodFillFaces
   , outerFaceFloodFill
   , facesAtEvenBarrierDepth
+  , FaceComponent
+  , faceComponentFaces
+  , BoundaryLoop
+  , boundaryLoopVertices
+  , RegionBoundary
+  , regionBoundaryOuterLoop
+  , regionBoundaryHoleLoops
+  , BoundaryObstruction (..)
+  , faceComponents
+  , labelledRegionBoundaries
+  , componentBoundary
+  , RadiusSquared
+  , mkRadiusSquared
+  , alphaShapeContainsFace
   ) where
 
+import Control.DeepSeq (NFData)
+import Data.Bifunctor (first)
+import qualified Data.IntMap.Strict as IntMap
 import qualified Data.IntSet as IntSet
+import Data.List (partition, unfoldr)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.Sequence as Seq
+import qualified Data.Vector as V
+import GHC.Generics (Generic)
 import Moonlight.Triangulation.Dcel
 import Moonlight.Triangulation.Handles.HandleDefs
 import Moonlight.Triangulation.Handles.Iterators.FixedIterators (undirectedEdges)
+import Moonlight.Triangulation.Internal.BoundaryCycle (simplifyBoundaryCycle)
 import Moonlight.Triangulation.Math
 import Moonlight.Triangulation.PointLocation
+import Moonlight.Triangulation.Scalar (circumradiusSquaredWithinCoordinates)
 import Moonlight.Triangulation.Types
 
+-- | One non-empty connected set of equally labelled bounded face indices.
+newtype FaceComponent = FaceComponent IntSet.IntSet
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Component faces in ascending DCEL order.
+faceComponentFaces :: FaceComponent -> [FaceId]
+faceComponentFaces (FaceComponent faces) =
+  fmap (FaceId . fromIntegral) (IntSet.toAscList faces)
+
+-- | One non-empty simple boundary loop. Outer loops are counter-clockwise and
+-- hole loops clockwise.
+newtype BoundaryLoop = BoundaryLoop
+  { boundaryLoopVertices :: NonEmpty VertexId
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | The authoritative boundary of one face component.
+data RegionBoundary = RegionBoundary
+  { regionBoundaryOuterLoop :: !BoundaryLoop
+  , regionBoundaryHoleLoops :: ![BoundaryLoop]
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Typed failure to descend boundary half-edges into simple polygon loops.
+data BoundaryObstruction
+  = BoundaryComponentFaceOutOfRange !FaceId {-# UNPACK #-} !Int
+  | BoundaryPinch !VertexId !DirectedEdgeId !DirectedEdgeId
+  | BoundaryCycleDidNotClose !DirectedEdgeId !DirectedEdgeId
+  | BoundaryLoopDegenerate ![VertexId]
+  | BoundaryOuterLoopCardinality !Int
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | An admitted finite, non-negative squared radius.
+newtype RadiusSquared = RadiusSquared Double
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Typed refusal shared by every squared-radius constructor.
+data RadiusSquaredError
+  = NonFiniteRadiusSquared !NonFiniteValue
+  | NegativeRadiusSquared !Double
+  deriving stock (Eq, Ord, Show)
+
+-- | Admit a finite, non-negative squared radius.
+mkRadiusSquared :: Double -> Either RadiusSquaredError RadiusSquared
+mkRadiusSquared value =
+  case classifyNonFinite value of
+    Just nonFinite -> Left (NonFiniteRadiusSquared nonFinite)
+    Nothing
+      | value < 0 -> Left (NegativeRadiusSquared value)
+      | otherwise -> Right (RadiusSquared value)
+
 -- | A query shape that can admit points, test edges, and supply a location
 -- seed.
 class DistanceMetric metric where
@@ -36,14 +121,13 @@
   metricStartPoint :: metric -> QueryPoint
 
 -- | An admitted center and squared radius.
-data CircleMetric = CircleMetric !(QueryPoint) !Double
+data CircleMetric = CircleMetric !QueryPoint !RadiusSquared
   deriving stock (Eq, Ord, Show)
 
 -- | Typed refusal for an invalid circle query.
 data CircleMetricError
   = InvalidCircleCenter !PointValidationError
-  | NonFiniteRadiusSquared !NonFiniteValue
-  | NegativeRadiusSquared !Double
+  | InvalidCircleRadius !RadiusSquaredError
   deriving stock (Eq, Ord, Show)
 
 -- | Admitted lower corner, upper corner, and center of an axis-aligned box.
@@ -59,13 +143,10 @@
 
 -- | A circle metric, or why the radius is unusable.
 circleMetric :: Point -> Double -> Either CircleMetricError CircleMetric
-circleMetric center radiusSquared = do
-  queryCenter <- either (Left . InvalidCircleCenter) Right (mkQueryPoint center)
-  case classifyNonFinite radiusSquared of
-    Just nonFinite -> Left (NonFiniteRadiusSquared nonFinite)
-    Nothing
-      | radiusSquared < 0 -> Left (NegativeRadiusSquared radiusSquared)
-      | otherwise -> Right (CircleMetric queryCenter radiusSquared)
+circleMetric center radiusSquared =
+  CircleMetric
+    <$> first InvalidCircleCenter (mkQueryPoint center)
+    <*> first InvalidCircleRadius (mkRadiusSquared radiusSquared)
 
 -- | An axis-aligned rectangle metric, or why the corners are unusable.
 rectangleMetric :: Point -> Point -> Either RectangleMetricError RectangleMetric
@@ -80,9 +161,9 @@
   Right (RectangleMetric queryLower queryUpper queryCenter)
 
 instance DistanceMetric CircleMetric where
-  metricContainsPoint (CircleMetric center radiusSquared) point =
+  metricContainsPoint (CircleMetric center (RadiusSquared radiusSquared)) point =
     squaredDistanceWide (queryPointValue center) point <= radiusSquared
-  metricIntersectsEdge (CircleMetric center radiusSquared) from to =
+  metricIntersectsEdge (CircleMetric center (RadiusSquared radiusSquared)) from to =
     segmentDistanceSquaredWide from to (queryPointValue center) <= radiusSquared
   metricStartPoint (CircleMetric center _) = center
 
@@ -174,7 +255,11 @@
   go [] _ accepted rejected result = (result, accepted, rejected)
   go (face : stack) visited accepted rejected result =
     let (stack', visited', accepted', rejected') =
-          foldl' expand (stack, visited, accepted, rejected) (faceDirectedEdges triangulation face)
+          foldFaceDirectedEdges'
+            triangulation
+            face
+            expand
+            (stack, visited, accepted, rejected)
      in go stack' visited' accepted' rejected' (face : result)
 
   expand (stack, visited, accepted, rejected) edge =
@@ -198,6 +283,199 @@
     | otherwise = (face : stack, IntSet.insert index visited)
    where
     index = fromIntegral value
+
+-- | Connected components of equally labelled bounded faces. Labels are
+-- evaluated once; the component carrier is the same 'IntSet' used by descent.
+faceComponents
+  :: Eq label
+  => Triangulation mode vertex directed undirected face
+  -> (FaceId -> label)
+  -> [(label, FaceComponent)]
+faceComponents triangulation labelFace = unfoldr descend initialUnvisited
+ where
+  labels =
+    V.generate
+      (numInnerFaces triangulation)
+      (\index -> labelFace (FaceId (fromIntegral (index + 1))))
+  initialUnvisited =
+    IntSet.fromRange (1, numFaces triangulation - 1)
+  labelAt faceIndex = labels V.!? (faceIndex - 1)
+
+  descend remaining =
+    case IntSet.minView remaining of
+      Nothing -> Nothing
+      Just (seedIndex, unseeded) ->
+        case labelAt seedIndex of
+          Nothing -> descend unseeded
+          Just componentLabel ->
+            let unvisited =
+                  collectComponent componentLabel (Seq.singleton seedIndex) unseeded
+                componentFaces = IntSet.difference remaining unvisited
+             in Just ((componentLabel, FaceComponent componentFaces), unvisited)
+
+  collectComponent componentLabel queued unvisited =
+    case Seq.viewl queued of
+      Seq.EmptyL -> unvisited
+      faceIndex Seq.:< remainingQueue ->
+        let face = FaceId (fromIntegral faceIndex)
+            (expandedQueue, remainingUnvisited) =
+              foldFaceDirectedEdges'
+                triangulation
+                face
+                (admitAdjacent componentLabel)
+                (remainingQueue, unvisited)
+         in collectComponent
+              componentLabel
+              expandedQueue
+              remainingUnvisited
+
+  admitAdjacent componentLabel (queued, unvisited) edge =
+    if
+      adjacent /= outerFace
+        && IntSet.member adjacentIndex unvisited
+        && labelAt adjacentIndex == Just componentLabel
+      then
+        ( queued Seq.|> adjacentIndex
+        , IntSet.delete adjacentIndex unvisited
+        )
+      else (queued, unvisited)
+   where
+    adjacent@(FaceId adjacentRaw) =
+      incidentFace triangulation (reverseEdge edge)
+    adjacentIndex = fromIntegral adjacentRaw
+
+-- | Descend every equally labelled bounded-face component through the one
+-- authoritative boundary tracer. Components are converted independently;
+-- callers may group equal labels only after this descent has succeeded.
+labelledRegionBoundaries
+  :: Eq label
+  => Triangulation mode vertex directed undirected face
+  -> (FaceId -> label)
+  -> Either BoundaryObstruction [(label, RegionBoundary)]
+labelledRegionBoundaries triangulation labelFace =
+  traverse
+    (\(label, component) -> (label,) <$> componentBoundary triangulation component)
+    (faceComponents triangulation labelFace)
+
+-- | Extract all simple boundary loops of a component from the DCEL. Boundary
+-- half-edges retain their incident component face on the left; this gives the
+-- outer loop counter-clockwise and holes clockwise without a later guess.
+componentBoundary
+  :: Triangulation mode vertex directed undirected face
+  -> FaceComponent
+  -> Either BoundaryObstruction RegionBoundary
+componentBoundary triangulation (FaceComponent componentFaces) = do
+  case IntSet.lookupGE (numFaces triangulation) componentFaces of
+    Just invalid ->
+      Left
+        ( BoundaryComponentFaceOutOfRange
+            (FaceId (fromIntegral invalid))
+            (numFaces triangulation)
+        )
+    Nothing -> Right ()
+  (boundaryEdges, outgoingSuccessor) <-
+    IntSet.foldl'
+      collectFaceBoundaryEdges
+      (Right (IntSet.empty, IntMap.empty))
+      componentFaces
+  orientedLoops <- traceBoundaryLoops triangulation outgoingSuccessor boundaryEdges
+  let (outer, holes) = partition ((== GT) . fst) orientedLoops
+      outerLoops = fmap snd outer
+      holeLoops = fmap snd holes
+  case outerLoops of
+    [outerLoop] ->
+      Right
+        RegionBoundary
+          { regionBoundaryOuterLoop = outerLoop
+          , regionBoundaryHoleLoops = holeLoops
+          }
+    _ -> Left (BoundaryOuterLoopCardinality (length outerLoops))
+ where
+  collectFaceBoundaryEdges boundaryGraph face =
+    foldFaceDirectedEdges'
+      triangulation
+      (FaceId (fromIntegral face))
+      insertBoundaryEdge
+      boundaryGraph
+
+  insertBoundaryEdge outcome edge = do
+    graph@(edges, successors) <- outcome
+    let FaceId adjacent = incidentFace triangulation (reverseEdge edge)
+    if IntSet.member (fromIntegral adjacent) componentFaces
+      then Right graph
+      else
+        let vertex@(VertexId rawVertex) = origin triangulation edge
+            vertexIndex = fromIntegral rawVertex
+            DirectedEdgeId rawEdge = edge
+         in case IntMap.lookup vertexIndex successors of
+              Just previousEdge -> Left (BoundaryPinch vertex previousEdge edge)
+              Nothing ->
+                Right
+                  ( IntSet.insert (fromIntegral rawEdge) edges
+                  , IntMap.insert vertexIndex edge successors
+                  )
+
+traceBoundaryLoops
+  :: Triangulation mode vertex directed undirected face
+  -> IntMap.IntMap DirectedEdgeId
+  -> IntSet.IntSet
+  -> Either BoundaryObstruction [(Ordering, BoundaryLoop)]
+traceBoundaryLoops triangulation outgoingByVertex = descend []
+ where
+  descend loops unvisited =
+    case IntSet.minView unvisited of
+      Nothing -> Right (reverse loops)
+      Just (rawStart, _) -> do
+        let start = DirectedEdgeId (fromIntegral rawStart)
+        (vertices, remaining) <- traceCycle start start unvisited []
+        oriented <- simplifyBoundaryLoop triangulation vertices
+        descend (oriented : loops) remaining
+
+  traceCycle start current unvisited reversedVertices =
+    let DirectedEdgeId rawCurrent = current
+        remaining = IntSet.delete (fromIntegral rawCurrent) unvisited
+        accumulated = origin triangulation current : reversedVertices
+        VertexId rawTarget = destination triangulation current
+     in case IntMap.lookup (fromIntegral rawTarget) outgoingByVertex of
+          Just successor
+            | successor == start -> Right (reverse accumulated, remaining)
+            | let DirectedEdgeId rawSuccessor = successor
+            , IntSet.member (fromIntegral rawSuccessor) remaining ->
+                traceCycle start successor remaining accumulated
+            | otherwise -> Left (BoundaryCycleDidNotClose start successor)
+          Nothing -> Left (BoundaryCycleDidNotClose start current)
+
+simplifyBoundaryLoop
+  :: Triangulation mode vertex directed undirected face
+  -> [VertexId]
+  -> Either BoundaryObstruction (Ordering, BoundaryLoop)
+simplifyBoundaryLoop triangulation =
+  fmap (\(orientation, vertices) -> (orientation, BoundaryLoop vertices))
+    . simplifyBoundaryCycle BoundaryLoopDegenerate redundant winding key
+ where
+  point vertex = vertexPoint triangulation vertex
+  redundant previousVertex current nextVertex =
+    orient2d (point previousVertex) (point current) (point nextVertex) == EQ
+      && onClosedSegment (point previousVertex) (point nextVertex) (point current)
+  winding previousVertex current nextVertex =
+    orient2d (point previousVertex) (point current) (point nextVertex)
+  key vertex = (point vertex, vertex)
+
+-- | Membership of a bounded face in the closed alpha shape. Exact dyadic
+-- comparison makes equality independent of circumcenter rounding.
+alphaShapeContainsFace
+  :: RadiusSquared
+  -> Triangulation 'Unconstrained vertex directed undirected face
+  -> FaceId
+  -> Bool
+alphaShapeContainsFace (RadiusSquared threshold) triangulation =
+  maybe False withinRadius . innerFaceVertices triangulation
+ where
+  withinRadius (firstVertex, secondVertex, thirdVertex) =
+    let Point ax ay = vertexPoint triangulation firstVertex
+        Point bx by = vertexPoint triangulation secondVertex
+        Point cx cy = vertexPoint triangulation thirdVertex
+     in circumradiusSquaredWithinCoordinates threshold ax ay bx by cx cy
 
 -- | Inner faces separated from the outer face by an even minimum number of
 -- barriers. A 0–1 BFS floods freely within one depth before crossing a barrier,
diff --git a/src-dcel/Moonlight/Triangulation/Internal/BoundaryCycle.hs b/src-dcel/Moonlight/Triangulation/Internal/BoundaryCycle.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/BoundaryCycle.hs
@@ -0,0 +1,136 @@
+-- | The shared algebra for simplifying and classifying an already-traced
+-- boundary cycle. Topology traversal remains in 'FloodFillIterator'; this
+-- module owns only fixed-point collinear deletion and winding observation.
+module Moonlight.Triangulation.Internal.BoundaryCycle
+  ( simplifyBoundaryCycle
+  , rotateCycleLeast
+  , rotateCycleLeastBy
+  , consecutivePairs
+  , unorderedPairs
+  , orderedPair
+  , cyclePairs
+  , cyclePairsNonEmpty
+  , cyclicTriples
+  ) where
+
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.List (tails)
+import qualified Data.List.NonEmpty as NonEmpty
+
+-- | Remove precisely the vertices admitted by @isRedundant@ until a fixed
+-- point is reached, then classify the winding at the least keyed retained
+-- vertex. The returned cycle preserves the tracer's start; publication layers
+-- may rotate their value-level observation independently. The caller supplies
+-- its obstruction constructor so the shared worker does not allocate a
+-- disposable intermediate error vocabulary at either specialization.
+simplifyBoundaryCycle
+  :: (Eq value, Ord key)
+  => ([value] -> obstruction)
+  -> (value -> value -> value -> Bool)
+  -> (value -> value -> value -> Ordering)
+  -> (value -> key)
+  -> [value]
+  -> Either obstruction (Ordering, NonEmpty value)
+simplifyBoundaryCycle obstruction isRedundant orientation key = descend
+ where
+  descend values@(_ : _ : _ : _) =
+    let triples = cyclicTriples values
+        retained =
+          [ current
+          | (previousValue, current, nextValue) <- triples
+          , not (isRedundant previousValue current nextValue)
+          ]
+     in if retained == values
+          then classify values triples
+          else descend retained
+  descend values = Left (obstruction values)
+
+  classify values triples =
+    case triples of
+      [] -> Left (obstruction values)
+      firstTriple : remainingTriples ->
+        let (previousValue, current, nextValue) =
+              foldl' chooseLeast firstTriple remainingTriples
+            winding = orientation previousValue current nextValue
+         in case (winding, values) of
+              (EQ, _) -> Left (obstruction values)
+              (_, initialValue : rest) -> Right (winding, initialValue :| rest)
+              _ -> Left (obstruction values)
+
+  chooseLeast selected@(_, selectedValue, _) candidate@(_, candidateValue, _)
+    | key candidateValue < key selectedValue = candidate
+    | otherwise = selected
+{-# INLINE simplifyBoundaryCycle #-}
+
+-- | Choose the least value as a cycle's observational origin without changing
+-- its orientation. Boundary publication and generated convex geometry share
+-- this one canonical rotation owner.
+rotateCycleLeast :: Ord value => NonEmpty value -> NonEmpty value
+rotateCycleLeast = rotateCycleLeastBy id
+{-# INLINE rotateCycleLeast #-}
+
+-- | Choose the least keyed value as a cycle's observational origin.
+rotateCycleLeastBy
+  :: Ord key
+  => (value -> key)
+  -> NonEmpty value
+  -> NonEmpty value
+rotateCycleLeastBy key values =
+  case break ((== minimumKey) . key) asList of
+    (before, selected : after) -> selected :| (after <> before)
+    _ -> values
+ where
+  asList = NonEmpty.toList values
+  minimumKey =
+    foldl'
+      (\selected candidate -> min selected (key candidate))
+      (key (NonEmpty.head values))
+      (NonEmpty.tail values)
+{-# INLINE rotateCycleLeastBy #-}
+
+-- | Every adjacent pair in a linear sequence.
+consecutivePairs :: [value] -> [(value, value)]
+consecutivePairs values = zip values (drop 1 values)
+{-# INLINE consecutivePairs #-}
+
+-- | Every unordered pair exactly once.
+unorderedPairs :: [value] -> [(value, value)]
+unorderedPairs values =
+  [(left, right) | left : remaining <- tails values, right <- remaining]
+{-# INLINE unorderedPairs #-}
+
+-- | Canonically orient an unordered pair.
+orderedPair :: Ord value => value -> value -> (value, value)
+orderedPair left right
+  | left <= right = (left, right)
+  | otherwise = (right, left)
+{-# INLINE orderedPair #-}
+
+-- | Every directed edge of a non-empty cycle in cycle order.
+cyclePairs :: NonEmpty value -> [(value, value)]
+cyclePairs = NonEmpty.toList . cyclePairsNonEmpty
+{-# INLINE cyclePairs #-}
+
+-- | The non-empty form of 'cyclePairs'. A singleton cycle has its sole value
+-- as both ends of its sole cyclic edge.
+cyclePairsNonEmpty :: NonEmpty value -> NonEmpty (value, value)
+cyclePairsNonEmpty values@(firstValue :| remaining) =
+  NonEmpty.zip values successors
+ where
+  successors =
+    case remaining of
+      [] -> firstValue :| []
+      nextValue : rest -> nextValue :| (rest <> [firstValue])
+{-# INLINE cyclePairsNonEmpty #-}
+
+-- | Consecutive cyclic triples, one centered at every value.
+cyclicTriples :: [value] -> [(value, value, value)]
+cyclicTriples values =
+  case values of
+    initial : second : remaining ->
+      let final = foldl' (\_ current -> current) initial (second : remaining)
+       in zip3
+            (final : values)
+            values
+            (second : remaining <> [initial])
+    _ -> []
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Canonical.hs b/src-dcel/Moonlight/Triangulation/Internal/Canonical.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/Canonical.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/Canonical.hs
@@ -57,7 +57,10 @@
   :: Triangulation mode vertex () () ()
   -> Either BuildError (Triangulation mode vertex () () ())
 canonicalize source = runST $ do
-  mutable <- newMutableDcel (triElementDefaults source) (max 1 vertexTotal)
+  mutable <-
+    newMutableDcel
+      (triElementDefaults source)
+      (exactDcelCapacity vertexTotal directedTotal faceTotal)
   forRange 0 vertexTotal $ \canonical -> do
     let !old = vertexOrder `U.unsafeIndex` canonical
     _ <-
diff --git a/src-dcel/Moonlight/Triangulation/Internal/CellSet.hs b/src-dcel/Moonlight/Triangulation/Internal/CellSet.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/CellSet.hs
@@ -0,0 +1,299 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs #-}
+
+-- | Invariant-bearing finite closed cell selections. The resident DCEL and
+-- the exact coordinates of precisely the selected vertices remain sealed
+-- together; no detached handle mask or whole-mesh projection exists.
+module Moonlight.Triangulation.Internal.CellSet
+  ( ExactCellSet (..)
+  , CellSelectionError (..)
+  , exactCellSet
+  , closeFaceCellSet
+  , closeExactCellSetWith
+  , exactCellSetVertexCount
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , foldExactCellVertices
+  , foldExactCellEdges
+  , foldExactCellFaces
+  , exactCellSetIsFaceClosure
+  ) where
+
+import Control.DeepSeq (NFData)
+import Data.Bifunctor (first)
+import Data.Foldable (traverse_)
+import qualified Data.IntMap.Strict as IntMap
+import qualified Data.IntSet as IntSet
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Dcel
+  ( faceDirectedEdges
+  , numFaces
+  , numUndirectedEdges
+  , numVertices
+  , undirectedEndpoints
+  , vertexPoint
+  )
+import Moonlight.Triangulation.Exact (ExactPoint, exactPointFromPoint)
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( FaceId (..)
+  , UndirectedEdgeId (..)
+  , VertexId (..)
+  , asUndirected
+  )
+import Moonlight.Triangulation.Internal.Representation (Triangulation)
+import Moonlight.Triangulation.Internal.Types (PointValidationError)
+
+-- | The keys of the exact-point map are the selected vertices; a second
+-- vertex set would merely be a disagreeable copy.
+data ExactCellSet where
+  ExactCellSet
+    :: !(Triangulation mode vertex directed undirected face)
+    -> !(IntMap.IntMap ExactPoint)
+    -> !IntSet.IntSet
+    -> !IntSet.IntSet
+    -> ExactCellSet
+
+data ClosedCellIds = ClosedCellIds
+  { closedVertexIds :: !IntSet.IntSet
+  , closedEdgeIds :: !IntSet.IntSet
+  , closedFaceIds :: !IntSet.IntSet
+  }
+
+data CellSelectionError
+  = CellVertexOutOfRange !VertexId !Int
+  | CellEdgeOutOfRange !UndirectedEdgeId !Int
+  | CellFaceOutOfRange !FaceId !Int
+  | CellOuterFaceSelected
+  | CellCoordinateInvalid !VertexId !PointValidationError
+  | CellEdgeBoundaryMissing !UndirectedEdgeId !VertexId
+  | CellFaceEdgeMissing !FaceId !UndirectedEdgeId
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | The strict authoring boundary validates handles and closure once, then
+-- converts only selected vertices.
+exactCellSet
+  :: Triangulation mode vertex directed undirected face
+  -> [VertexId]
+  -> [UndirectedEdgeId]
+  -> [FaceId]
+  -> Either CellSelectionError ExactCellSet
+exactCellSet triangulation selectedVertices selectedEdges selectedFaces = do
+  validateHandles triangulation selectedVertices selectedEdges selectedFaces
+  let verticesSet = vertexSet selectedVertices
+      edgesSet = edgeSet selectedEdges
+      facesSet = faceSet selectedFaces
+  traverse_ (validateEdgeClosure triangulation verticesSet) selectedEdges
+  traverse_ (validateFaceClosure triangulation edgesSet) selectedFaces
+  sealCellSet
+    (ordinaryExactPoint triangulation)
+    triangulation
+    (ClosedCellIds verticesSet edgesSet facesSet)
+
+-- | Close a bounded face selection over all resident boundary cells. Face
+-- handles are author input and are checked; constructed closure is not then
+-- pointlessly proved a second time.
+closeFaceCellSet
+  :: Triangulation mode vertex directed undirected face
+  -> [FaceId]
+  -> Either CellSelectionError ExactCellSet
+closeFaceCellSet triangulation selectedFaces = do
+  validateHandles triangulation [] [] selectedFaces
+  sealCellSet
+    (ordinaryExactPoint triangulation)
+    triangulation
+    (closeCellIds triangulation [] [] selectedFaces)
+
+-- | Seal closure derived by a trusted downstream owner. Overlay supplies
+-- handles from this very DCEL, so repeating range and closure validation would
+-- protect against an impossible phase while charging every selection for it.
+closeExactCellSetWith
+  :: (VertexId -> Either CellSelectionError ExactPoint)
+  -> Triangulation mode vertex directed undirected face
+  -> [VertexId]
+  -> [UndirectedEdgeId]
+  -> [FaceId]
+  -> Either CellSelectionError ExactCellSet
+closeExactCellSetWith exactPointAt triangulation explicitVertices explicitEdges selectedFaces =
+  sealCellSet
+    exactPointAt
+    triangulation
+    (closeCellIds triangulation explicitVertices explicitEdges selectedFaces)
+
+ordinaryExactPoint
+  :: Triangulation mode vertex directed undirected face
+  -> VertexId
+  -> Either CellSelectionError ExactPoint
+ordinaryExactPoint triangulation vertex =
+  first (CellCoordinateInvalid vertex)
+    (exactPointFromPoint (vertexPoint triangulation vertex))
+
+sealCellSet
+  :: (VertexId -> Either CellSelectionError ExactPoint)
+  -> Triangulation mode vertex directed undirected face
+  -> ClosedCellIds
+  -> Either CellSelectionError ExactCellSet
+sealCellSet exactPointAt triangulation closed = do
+  exactPoints <- exactPointsFor exactPointAt (closedVertexIds closed)
+  pure
+    ( ExactCellSet
+        triangulation
+        exactPoints
+        (closedEdgeIds closed)
+        (closedFaceIds closed)
+    )
+
+closeCellIds
+  :: Triangulation mode vertex directed undirected face
+  -> [VertexId]
+  -> [UndirectedEdgeId]
+  -> [FaceId]
+  -> ClosedCellIds
+closeCellIds triangulation explicitVertices explicitEdges selectedFaces =
+  let faceEdges =
+        concatMap
+          (map asUndirected . faceDirectedEdges triangulation)
+          selectedFaces
+      edges = edgeSet (explicitEdges <> faceEdges)
+      closedEdges = map (UndirectedEdgeId . fromIntegral) (IntSet.toAscList edges)
+      edgeVertices =
+        concatMap
+          (\edge ->
+             let (from, to) = undirectedEndpoints triangulation edge
+              in [from, to])
+          closedEdges
+      vertices =
+        vertexSet
+          ( explicitVertices
+              <> edgeVertices
+          )
+   in ClosedCellIds
+        { closedVertexIds = vertices
+        , closedEdgeIds = edges
+        , closedFaceIds = faceSet selectedFaces
+        }
+
+exactPointsFor
+  :: (VertexId -> Either CellSelectionError ExactPoint)
+  -> IntSet.IntSet
+  -> Either CellSelectionError (IntMap.IntMap ExactPoint)
+exactPointsFor exactPointAt selected =
+  IntMap.fromAscList
+    <$> traverse
+      (\index -> do
+         point <- exactPointAt (VertexId (fromIntegral index))
+         pure (index, point))
+      (IntSet.toAscList selected)
+
+validateHandles
+  :: Triangulation mode vertex directed undirected face
+  -> [VertexId]
+  -> [UndirectedEdgeId]
+  -> [FaceId]
+  -> Either CellSelectionError ()
+validateHandles triangulation selectedVertices selectedEdges selectedFaces = do
+  traverse_ validateVertex selectedVertices
+  traverse_ validateEdge selectedEdges
+  traverse_ validateFace selectedFaces
+ where
+  validateVertex vertex@(VertexId raw)
+    | fromIntegral raw < numVertices triangulation = Right ()
+    | otherwise = Left (CellVertexOutOfRange vertex (numVertices triangulation))
+  validateEdge edge@(UndirectedEdgeId raw)
+    | fromIntegral raw < numUndirectedEdges triangulation = Right ()
+    | otherwise = Left (CellEdgeOutOfRange edge (numUndirectedEdges triangulation))
+  validateFace face@(FaceId raw)
+    | raw == 0 = Left CellOuterFaceSelected
+    | fromIntegral raw < numFaces triangulation = Right ()
+    | otherwise = Left (CellFaceOutOfRange face (numFaces triangulation))
+
+validateEdgeClosure
+  :: Triangulation mode vertex directed undirected face
+  -> IntSet.IntSet
+  -> UndirectedEdgeId
+  -> Either CellSelectionError ()
+validateEdgeClosure triangulation selectedVertices edge =
+  traverse_ requireVertex [from, to]
+ where
+  (from, to) = undirectedEndpoints triangulation edge
+  requireVertex vertex@(VertexId raw)
+    | IntSet.member (fromIntegral raw) selectedVertices = Right ()
+    | otherwise = Left (CellEdgeBoundaryMissing edge vertex)
+
+validateFaceClosure
+  :: Triangulation mode vertex directed undirected face
+  -> IntSet.IntSet
+  -> FaceId
+  -> Either CellSelectionError ()
+validateFaceClosure triangulation selectedEdges face =
+  traverse_ (requireEdge . asUndirected) (faceDirectedEdges triangulation face)
+ where
+  requireEdge edge@(UndirectedEdgeId raw)
+    | IntSet.member (fromIntegral raw) selectedEdges = Right ()
+    | otherwise = Left (CellFaceEdgeMissing face edge)
+
+vertexSet :: [VertexId] -> IntSet.IntSet
+vertexSet = IntSet.fromList . map (\(VertexId raw) -> fromIntegral raw)
+
+edgeSet :: [UndirectedEdgeId] -> IntSet.IntSet
+edgeSet = IntSet.fromList . map (\(UndirectedEdgeId raw) -> fromIntegral raw)
+
+faceSet :: [FaceId] -> IntSet.IntSet
+faceSet = IntSet.fromList . map (\(FaceId raw) -> fromIntegral raw)
+
+exactCellSetVertexCount :: ExactCellSet -> Int
+exactCellSetVertexCount (ExactCellSet _ selected _ _) = IntMap.size selected
+
+exactCellSetEdgeCount :: ExactCellSet -> Int
+exactCellSetEdgeCount (ExactCellSet _ _ selected _) = IntSet.size selected
+
+exactCellSetFaceCount :: ExactCellSet -> Int
+exactCellSetFaceCount (ExactCellSet _ _ _ selected) = IntSet.size selected
+
+foldExactCellVertices
+  :: (accumulator -> VertexId -> ExactPoint -> accumulator)
+  -> accumulator
+  -> ExactCellSet
+  -> accumulator
+foldExactCellVertices step initial (ExactCellSet _ selected _ _) =
+  IntMap.foldlWithKey'
+    (\accumulator index point ->
+       step accumulator (VertexId (fromIntegral index)) point)
+    initial
+    selected
+
+foldExactCellEdges
+  :: (accumulator -> UndirectedEdgeId -> accumulator)
+  -> accumulator
+  -> ExactCellSet
+  -> accumulator
+foldExactCellEdges step initial (ExactCellSet _ _ selected _) =
+  IntSet.foldl'
+    (\accumulator index -> step accumulator (UndirectedEdgeId (fromIntegral index)))
+    initial
+    selected
+
+foldExactCellFaces
+  :: (accumulator -> FaceId -> accumulator)
+  -> accumulator
+  -> ExactCellSet
+  -> accumulator
+foldExactCellFaces step initial (ExactCellSet _ _ _ selected) =
+  IntSet.foldl'
+    (\accumulator index -> step accumulator (FaceId (fromIntegral index)))
+    initial
+    selected
+
+-- | Whether the value contains exactly the downward closure of its selected
+-- faces, with no additional isolated vertex or edge cells. This is the precise
+-- admission condition for the conventional polygonal perimeter projection.
+exactCellSetIsFaceClosure :: ExactCellSet -> Bool
+exactCellSetIsFaceClosure (ExactCellSet triangulation points edges faces) =
+  let selectedFaces =
+        map (FaceId . fromIntegral) (IntSet.toAscList faces)
+      closed = closeCellIds triangulation [] [] selectedFaces
+   in IntMap.keysSet points == closedVertexIds closed
+        && edges == closedEdgeIds closed
+        && faces == closedFaceIds closed
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations.hs
@@ -14,25 +14,33 @@
   , lineToArea
   , insertIntoFace
   , insertOnEdge
+  , ReservedSweepCells
+  , SweepCellCursor
+  , SweepInsertion (..)
+  , reserveSweepCells
+  , initialSweepCellCursor
+  , commitReservedSweepConnections
   , insertOutsideHull
-  , insertOutsideHullBetween
+  , insertOutsideHullAtEdge
   , closeOuterTurn
+  , closeOuterTurnReserved
   , fixHullConvexity
   , flipEdge
   , legalizeScratch
+  , legalizeStarEdge
   , legalizeEdges
   , legalizeCavityFanScratch
   , LegalizationLaw (..)
   , seedStarScratch
   , seedGenericEdges
   , drainLegalization
-  , noStarVertex
+  , CandidateDiscipline (..)
   , isFlippableEdge
   , collectLineChain
   ) where
 
 import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
-  ( noStarVertex
+  ( CandidateDiscipline (..)
   , seedGenericEdges
   , seedStarScratch
   )
@@ -50,15 +58,23 @@
   , isFlippableEdge
   )
 import Moonlight.Triangulation.Internal.DcelOperations.Hull
-  ( closeOuterTurn
+  ( ReservedSweepCells
+  , SweepCellCursor
+  , SweepInsertion (..)
+  , closeOuterTurn
+  , closeOuterTurnReserved
+  , commitReservedSweepConnections
   , fixHullConvexity
+  , initialSweepCellCursor
   , insertOutsideHull
-  , insertOutsideHullBetween
+  , insertOutsideHullAtEdge
+  , reserveSweepCells
   )
 import Moonlight.Triangulation.Internal.DcelOperations.Legalize
   ( legalizeCavityFanScratch
   , legalizeEdges
   , legalizeScratch
+  , legalizeStarEdge
   )
 import Moonlight.Triangulation.Internal.DcelOperations.Normalize (drainLegalization)
 import Moonlight.Triangulation.Internal.DcelOperations.Subdivide
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs
@@ -5,42 +5,35 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
 
--- | The legalization candidate stack: tagging, growth, and seeding.
+-- | The legalization candidate stack: discipline, growth, and seeding.
 module Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
-  ( starCandidate
-  , genericCandidate
+  ( CandidateDiscipline (..)
   , growLegalizationArena
   , seedStarScratch
+  , seedGenericPairInArena
   , seedGenericEdges
-  , noStarVertex
   ) where
 
 import Control.Monad (forM_, when)
 import Control.Monad.ST (ST)
-import Data.Word (Word32)
 import qualified Data.Vector.Unboxed.Mutable as MUV
 import Moonlight.Triangulation.Internal.OperationState
-  ( OperationState
+  ( LegalizationArena (..)
+  , OperationState
   , legalizationArena
   , readScratch
   , storeLegalizationArena
   )
 import Moonlight.Triangulation.Internal.PackedIndex (packIndex)
 
--- A candidate carries the discipline under which it must be tested. One
--- seeded from an insertion star names an edge that has to be re-oriented so
--- the inserted vertex is opposite it; one seeded generically already names the
--- edge to test. Building a geometric patch used to start and drain the work
--- stack once per primitive because the two could not share it — the fan, then
--- every closed hull turn separately, each re-reading the same neighbourhood.
--- With the discipline travelling on the candidate they share one drain.
-starCandidate :: Int -> Int
-starCandidate edge = edge * 2
-{-# INLINE starCandidate #-}
-
-genericCandidate :: Int -> Int
-genericCandidate edge = edge * 2 + 1
-{-# INLINE genericCandidate #-}
+-- | Every normalization epoch is homogeneous. A star epoch turns each edge so
+-- the inserted vertex is the opposite apex; a generic epoch consumes the
+-- directed edge exactly as seeded. Keeping that fact at the epoch boundary
+-- prevents every candidate from carrying and decoding a tag for a distinction
+-- that cannot vary inside the stack.
+data CandidateDiscipline
+  = StarCandidates !Int
+  | GenericCandidates
 
 -- | Seed candidates from the scratch arena, returning the new stack top.
 -- Transaction-sized preallocation covers the normal path; rare adversarial
@@ -49,33 +42,54 @@
 seedStarScratch operation top candidateCount = do
   initialArena <- legalizationArena operation
   arena <- growLegalizationArena initialArena (top + candidateCount)
-  when (MUV.length arena /= MUV.length initialArena) (storeLegalizationArena operation arena)
+  when (legalizationArenaLength arena /= legalizationArenaLength initialArena) (storeLegalizationArena operation arena)
+  let LegalizationArena values = arena
   forM_ [0 .. candidateCount - 1] $ \index -> do
     edge <- readScratch operation index
-    MUV.unsafeWrite arena (top + index) (packIndex (starCandidate edge))
+    MUV.unsafeWrite values (top + index) (packIndex edge)
   pure (top + candidateCount)
 
+-- | Append the fixed two-edge section produced by one closed hull turn.
+-- Materializing @[left, right]@ only to count, zip and traverse it made the
+-- dominant sweep rewrite pay list traffic for an arity known by construction.
+-- The arena is explicit because a circle sweep borrows it once and glues it
+-- back to the operation once, rather than performing three reference lookups
+-- around every inserted point.
+seedGenericPairInArena
+  :: LegalizationArena s
+  -> Int
+  -> Int
+  -> Int
+  -> ST s (LegalizationArena s, Int)
+seedGenericPairInArena initialArena top left right = do
+  let !nextTop = top + 2
+  arena <- growLegalizationArena initialArena nextTop
+  let LegalizationArena values = arena
+  MUV.unsafeWrite values top (packIndex left)
+  MUV.unsafeWrite values (top + 1) (packIndex right)
+  pure (arena, nextTop)
+{-# INLINE seedGenericPairInArena #-}
+
 -- | Seed generic candidates from a list, returning the new stack top.
 seedGenericEdges :: OperationState s -> Int -> [Int] -> ST s Int
 seedGenericEdges operation top edges = do
   initialArena <- legalizationArena operation
   let !count = length edges
   arena <- growLegalizationArena initialArena (top + count)
-  when (MUV.length arena /= MUV.length initialArena) (storeLegalizationArena operation arena)
+  when (legalizationArenaLength arena /= legalizationArenaLength initialArena) (storeLegalizationArena operation arena)
+  let LegalizationArena values = arena
   forM_ (zip [0 ..] edges) $ \(!index, !edge) ->
-    MUV.unsafeWrite arena (top + index) (packIndex (genericCandidate edge))
+    MUV.unsafeWrite values (top + index) (packIndex edge)
   pure (top + count)
 
-growLegalizationArena :: MUV.MVector s Word32 -> Int -> ST s (MUV.MVector s Word32)
-growLegalizationArena arena required
+growLegalizationArena :: LegalizationArena s -> Int -> ST s (LegalizationArena s)
+growLegalizationArena arena@(LegalizationArena values) required
   | required <= current = pure arena
-  | otherwise = MUV.grow arena (max (required - current) (max 1 current))
+  | otherwise = LegalizationArena <$> MUV.grow values (max (required - current) (max 1 current))
  where
-  !current = MUV.length arena
+  !current = MUV.length values
 {-# INLINE growLegalizationArena #-}
 
--- | No star candidate can be seeded against this, so a drain given it must
--- have been seeded generically throughout.
-noStarVertex :: Int
-noStarVertex = -1
-
+legalizationArenaLength :: LegalizationArena s -> Int
+legalizationArenaLength (LegalizationArena values) = MUV.length values
+{-# INLINE legalizationArenaLength #-}
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRewrite.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRewrite.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRewrite.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRewrite.hs
@@ -17,6 +17,7 @@
 import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex)
 import Moonlight.Triangulation.Internal.Mutable
   ( MutableDcel
+  , MutableTopology (..)
   , payloadsPristine
   , readConstraint
   , readFace
@@ -25,9 +26,6 @@
   , readPrevious
   , resetEdgeData
   , resetFaceData
-  , setCycle3
-  , writeOrigin
-  , writeVertexOut
   )
 import Moonlight.Triangulation.Internal.Types (BuildError (ConstrainedEdgeFlipRefused))
 
@@ -57,7 +55,8 @@
 -- than making 'flipEdge' fetch it a second time; 'flipEdge' is that fetch, for
 -- callers arriving with nothing but an index.
 applyFlip
-  :: MutableDcel s vertex directed undirected face
+  :: MutableTopology mutable
+  => mutable s vertex directed undirected face
   -> Int
   -> Int
   -> Int
@@ -71,11 +70,31 @@
   -> Int
   -> Int
   -> ST s ()
-applyFlip mutable edge twin edgeNext edgePrevious twinNext twinPrevious leftFace rightFace a b c d = do
-  writeOrigin mutable edge c
-  writeOrigin mutable twin d
-  setCycle3 mutable leftFace edge twinPrevious edgeNext
-  setCycle3 mutable rightFace twin edgePrevious twinNext
+applyFlip topology edge twin edgeNext edgePrevious twinNext twinPrevious leftFace rightFace a b c d = do
+  let !mutable = topologyOwner topology
+  -- Rewrite only fields whose denotation changes. Re-stating both complete
+  -- triangles writes the two unchanged face labels and face anchors, then
+  -- needlessly redirects the two new diagonal endpoints even though each
+  -- already owns another live outgoing edge. The local quadrilateral proof
+  -- above names every changed adjacency, so the minimal section is exact.
+  topologyWriteOrigin topology edge c
+  topologyWriteOrigin topology twin d
+  topologyWriteNext topology edgeNext edge
+  topologyWritePrevious topology edgeNext twinPrevious
+  topologyWriteNext topology edge twinPrevious
+  topologyWritePrevious topology edge edgeNext
+  topologyWriteNext topology twinPrevious edgeNext
+  topologyWritePrevious topology twinPrevious edge
+  topologyWriteFace topology twinPrevious leftFace
+  topologyWriteFaceEdge topology leftFace edge
+  topologyWriteNext topology twinNext twin
+  topologyWritePrevious topology twinNext edgePrevious
+  topologyWriteNext topology twin edgePrevious
+  topologyWritePrevious topology twin twinNext
+  topologyWriteNext topology edgePrevious twinNext
+  topologyWritePrevious topology edgePrevious twin
+  topologyWriteFace topology edgePrevious rightFace
+  topologyWriteFaceEdge topology rightFace twin
   -- The diagonal AB is gone and CD stands in its slot; both triangles have
   -- swapped a corner. Three elements changed what they are, so three labels go.
   -- Each reset carries the same test; a site doing several states it once.
@@ -83,8 +102,6 @@
     resetEdgeData mutable (edge `quot` 2)
     resetFaceData mutable leftFace
     resetFaceData mutable rightFace
-  writeVertexOut mutable a twinNext
-  writeVertexOut mutable b edgeNext
-  writeVertexOut mutable c edge
-  writeVertexOut mutable d twin
+  topologyWriteVertexOut topology a twinNext
+  topologyWriteVertexOut topology b edgeNext
 {-# INLINE applyFlip #-}
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRule.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRule.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRule.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRule.hs
@@ -31,9 +31,10 @@
 -- where @ab@ is the diagonal and @c@, @d@ the apexes opposite it. The
 -- coordinates arrive raw, in the order the mutable accessors carry.
 --
--- '&&' gives 'ValidMesh' its short-circuit past the incircle determinant, and
--- 'CavityRepair' never names the convexity test whose premise it denies. The
--- drain reaches this with the quadrilateral already in hand; the firing
+-- Both interpreters reach the same incircle rule with different eligibility
+-- proofs. 'ValidMesh' arrives from two consistently oriented incident faces;
+-- 'CavityRepair' restricts firing to the new fan through its floor witness.
+-- The drain reaches this with the quadrilateral already in hand; the firing
 -- condition itself has one owner.
 diagonalFires
   :: LegalizationLaw
@@ -41,10 +42,13 @@
   -> Bool
 diagonalFires law ax ay bx by cx cy dx dy =
   case law of
-    ValidMesh ->
-      orient2dCoordinates cx cy dx dy bx by == GT
-        && orient2dCoordinates dx dy cx cy ax ay == GT
-        && illegalDiagonal ax ay bx by cx cy dx dy
+    -- A valid triangulation already proves the two incident triangles are
+    -- consistently oriented. If their union is concave, the opposite apex is
+    -- outside the first triangle's circumcircle and the in-circle rule refuses
+    -- the flip; re-running two exact orientation predicates merely re-proves
+    -- that premise for every candidate. This is the same lawful section used
+    -- by Spade's removal legalizer: the diagonal rule alone decides.
+    ValidMesh -> illegalDiagonal ax ay bx by cx cy dx dy
     CavityRepair _ -> illegalDiagonal ax ay bx by cx cy dx dy
 {-# INLINE diagonalFires #-}
 
@@ -106,9 +110,9 @@
 -- be applied, never what a legal diagonal is.
 --
 -- 'ValidMesh' legalizes a mesh that is already a triangulation. There a
--- non-convex quadrilateral's diagonal is necessarily locally Delaunay, so
--- testing convexity first is a free short-circuit around the incircle
--- determinant — it can only decline flips the determinant would have declined.
+-- non-convex quadrilateral's diagonal is necessarily locally Delaunay, so the
+-- incircle rule itself declines the flip; separately re-running two exact
+-- orientation predicates would only re-prove the incident-face premise.
 --
 -- 'CavityRepair' legalizes the fan that fills a removed vertex's hole, which is
 -- not yet a triangulation. That implication fails on an inverted quadrilateral,
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs
@@ -4,39 +4,71 @@
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}
 
 -- | Growth outside the hull: visible ranges, turn closure, and convexity repair.
 module Moonlight.Triangulation.Internal.DcelOperations.Hull
-  ( insertOutsideHull
-  , insertOutsideHullBetween
+  ( ReservedSweepCells
+  , SweepCellCursor
+  , SweepInsertion (..)
+  , reserveSweepCells
+  , initialSweepCellCursor
+  , commitReservedSweepConnections
+  , insertOutsideHull
+  , insertOutsideHullAtEdge
   , closeOuterTurn
+  , closeOuterTurnReserved
   , fixHullConvexity
   ) where
 
-import Control.Monad (forM_)
+import Control.Monad (forM_, when)
 import Control.Monad.ST (ST)
 import Data.STRef (writeSTRef)
 import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..), FaceId (..))
 import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
-  ( noStarVertex
-  , seedGenericEdges
+  ( seedGenericPairInArena
   )
-import Moonlight.Triangulation.Internal.DcelOperations.FlipRule (LegalizationLaw (..))
-import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeScratch)
-import Moonlight.Triangulation.Internal.DcelOperations.Normalize (drainLegalization)
+import Moonlight.Triangulation.Internal.DcelOperations.Legalize
+  ( legalizeScratch
+  , legalizeDenseStarEdgeInArena
+  )
+import Moonlight.Triangulation.Internal.DcelOperations.Normalize
+  ( LegalizationDrain (..)
+  , drainDenseUnconstrainedGenericLegalization
+  )
 import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex)
 import Moonlight.Triangulation.Internal.Mutable
-  ( MutableDcel (..)
+  ( DenseMutableDcel
+  , MutableDcel (..)
   , addEdge
   , addEdgeBlock
   , addFaceBlock
   , directedEdgeCount
+  , denseCommitFreshConnections
+  , denseInitializeUnconstrainedEdgeBlock
+  , denseLinkEdges
+  , denseMarkFreshConnected
+  , denseMutableOwner
+  , denseReadFace
+  , denseReadFaceEdge
+  , denseReadNext
+  , denseReadOrigin
+  , denseReadPointX
+  , denseReadPointY
+  , denseReadPrevious
+  , denseSetCycle3
+  , denseWriteFace
+  , denseWriteFaceEdge
+  , denseWriteNext
+  , denseWriteOrigin
+  , denseWritePrevious
+  , denseWriteVertexOut
   , ensureCellCapacity
+  , faceCount
   , linkEdges
   , markConnected
   , pointAt
   , readFace
-  , readFaceEdge
   , readNext
   , readOrigin
   , readPointX
@@ -45,11 +77,14 @@
   , setCycle3
   , writeFace
   , writeFaceEdge
+  , writeNext
   , writeOrigin
+  , writePrevious
   , writeVertexOut
   )
 import Moonlight.Triangulation.Internal.OperationState
   ( Counter (..)
+  , LegalizationArena
   , OperationState
   , addCounter
   , readScratch
@@ -59,6 +94,74 @@
 import Moonlight.Triangulation.Types (BuildError (..), Point (..))
 import Moonlight.Triangulation.Scalar (orient2dCoordinates)
 
+-- | Proof that the remaining circle-sweep program fits the particular mutable
+-- arena carried here. The constructor is private: only 'reserveSweepCells' can
+-- pair a DCEL with the one-time capacity check. Carrying the DCEL inside the
+-- witness prevents a proof for one mutable mesh from being applied to another
+-- mesh that happens to share the same @ST@ region.
+data ReservedSweepCells s vertex directed undirected face = ReservedSweepCells
+  !(DenseMutableDcel s vertex directed undirected face)
+  {-# UNPACK #-} !SweepCellCursor
+
+-- | The uncommitted directed-edge and face cardinalities of one reserved
+-- sweep section. Its constructor is private: only the reservation can mint the
+-- initial cursor, and only reserved topology rewrites can advance it.
+data SweepCellCursor = SweepCellCursor
+  {-# UNPACK #-} !Int
+  {-# UNPACK #-} !Int
+
+-- | The outer edges glued by one direct sweep insertion and the normalization
+-- work discharged while its star still existed. The sweep accumulates these
+-- strict metrics and charges the operation counters once, instead of mutating
+-- diagnostic cells once per point.
+data SweepInsertion s
+  = SweepInsertionFailure !BuildError
+  | SweepInsertion
+      {-# UNPACK #-} !Int
+      {-# UNPACK #-} !Int
+      {-# UNPACK #-} !Int
+      {-# UNPACK #-} !Int
+      !(LegalizationArena s)
+      {-# UNPACK #-} !SweepCellCursor
+
+-- | Discharge the monotone circle-sweep allocation budget once. Every
+-- remaining point can contribute at most three undirected edges and two faces
+-- to a planar triangulation. The seed already occupies its own cells, so this
+-- bound is stated only over the points the sweep has not connected yet.
+reserveSweepCells
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Int
+  -> ST s (Either BuildError (ReservedSweepCells s vertex directed undirected face))
+reserveSweepCells dense remainingPoints = do
+  let !mutable = denseMutableOwner dense
+  halfEdges <- directedEdgeCount mutable
+  faces <- faceCount mutable
+  capacity <- ensureCellCapacity mutable (3 * remainingPoints) (2 * remainingPoints)
+  pure (ReservedSweepCells dense (SweepCellCursor halfEdges faces) <$ capacity)
+
+initialSweepCellCursor
+  :: ReservedSweepCells s vertex directed undirected face
+  -> SweepCellCursor
+initialSweepCellCursor (ReservedSweepCells _ cursor) = cursor
+{-# INLINE initialSweepCellCursor #-}
+
+-- | Commit the cardinality of the fresh connectivity sections materialized by
+-- direct sweep insertions. Their per-vertex bits and outgoing edges are already
+-- present; this is the one global descent step before any skipped point enters
+-- the ordinary insertion interpreter.
+commitReservedSweepConnections
+  :: ReservedSweepCells s vertex directed undirected face
+  -> SweepCellCursor
+  -> Int
+  -> ST s ()
+commitReservedSweepConnections (ReservedSweepCells dense _) (SweepCellCursor halfEdges faces) inserted = do
+  denseCommitFreshConnections dense inserted
+  let !MutableDcel{mdHalfCount, mdFaceCount, mdLastFace} = denseMutableOwner dense
+  writeSTRef mdHalfCount halfEdges
+  writeSTRef mdFaceCount faces
+  when (inserted > 0 && faces > 1) (writeSTRef mdLastFace (faces - 1))
+{-# INLINE commitReservedSweepConnections #-}
+
 insertOutsideHull :: forall p s vertex directed undirected face. KnownProbe p => MutableDcel s vertex directed undirected face -> OperationState s -> Int -> Int -> ST s (Either BuildError ())
 insertOutsideHull mutable operation start vertex = do
   query <- pointAt mutable vertex
@@ -102,24 +205,67 @@
             visible <- visibleOuter mutable candidate query
             if visible then expandNext (bound - 1) candidate left query else pure current
 
--- | Insert a vertex outside an explicitly selected contiguous outer-face range.
--- The range, rather than a convexity policy, is the primitive: regular
--- insertion supplies the complete visible range, while circle sweep may supply
--- one edge and defer the remaining hull turns. Topology mutation has one owner.
-insertOutsideHullBetween
-  :: forall p s vertex directed undirected face
-   . KnownProbe p
-  => MutableDcel s vertex directed undirected face
-  -> OperationState s
+-- | Replace one selected outer edge @a->b@ by @a->v, v->b@ and materialize
+-- the covered triangle. This is the circle sweep's actual local section: the
+-- ordinary outside-hull path continues to own arbitrary visible ranges, while
+-- the sweep no longer collects a singleton range into shared scratch merely to
+-- rediscover its first and last edge.
+insertOutsideHullAtEdge
+  :: ReservedSweepCells s vertex directed undirected face
+  -> SweepCellCursor
+  -> LegalizationArena s
   -> Int
   -> Int
   -> Int
-  -> ST s (Either BuildError (Int, Int))
-insertOutsideHullBetween mutable operation left right vertex = do
-  collected <- collectOuterChain mutable operation left right
-  case collected of
-    Left obstruction -> pure (Left obstruction)
-    Right chainCount -> insertOutsideHullCollected @p mutable operation vertex chainCount
+  -> Int
+  -> ST s (SweepInsertion s)
+insertOutsideHullAtEdge (ReservedSweepCells dense _) (SweepCellCursor nextHalf nextFace) arena outerEdge from to vertex = do
+  incident <- denseReadFace dense outerEdge
+  if incident /= 0
+    then
+      pure
+        ( SweepInsertionFailure
+            ( OuterRangeContainsInnerEdge
+                (DirectedEdgeId (fromIntegral outerEdge))
+                (FaceId (fromIntegral incident))
+            )
+        )
+    else do
+      oldPrevious <- denseReadPrevious dense outerEdge
+      oldNext <- denseReadNext dense outerEdge
+      denseInitializeUnconstrainedEdgeBlock dense nextHalf 2
+      let !edgeBase = nextHalf
+          !face = nextFace
+          !firstOuterSpoke = edgeBase
+          !firstInnerSpoke = edgeBase + 1
+          !secondInnerSpoke = edgeBase + 2
+          !lastOuterSpoke = edgeBase + 3
+      denseWriteOrigin dense firstOuterSpoke from
+      denseWriteOrigin dense firstInnerSpoke vertex
+      denseWriteOrigin dense secondInnerSpoke to
+      denseWriteOrigin dense lastOuterSpoke vertex
+      denseSetCycle3 dense face outerEdge secondInnerSpoke firstInnerSpoke
+      denseWriteFace dense firstOuterSpoke 0
+      denseWriteFace dense lastOuterSpoke 0
+      denseLinkEdges dense oldPrevious firstOuterSpoke
+      denseLinkEdges dense firstOuterSpoke lastOuterSpoke
+      denseLinkEdges dense lastOuterSpoke oldNext
+      denseWriteFaceEdge dense 0 firstOuterSpoke
+      denseWriteVertexOut dense from outerEdge
+      denseWriteVertexOut dense to secondInnerSpoke
+      denseMarkFreshConnected dense vertex lastOuterSpoke
+      LegalizationDrain flips maxDepth () finalArena <-
+        legalizeDenseStarEdgeInArena dense arena vertex outerEdge
+      pure
+        ( SweepInsertion
+            firstOuterSpoke
+            lastOuterSpoke
+            flips
+            maxDepth
+            finalArena
+            (SweepCellCursor (nextHalf + 4) (nextFace + 1))
+        )
+{-# INLINE insertOutsideHullAtEdge #-}
 
 collectOuterChain
   :: MutableDcel s vertex directed undirected face
@@ -228,24 +374,94 @@
   capacity <- ensureCellCapacity mutable 1 1
   case capacity of
     Left obstruction -> pure (Left obstruction)
-    Right () -> do
-      second <- readNext mutable first
-      oldPrevious <- readPrevious mutable first
-      oldNext <- readNext mutable second
-      from <- readOrigin mutable first
-      to <- readOrigin mutable (reverseIndex second)
-      (outer, inner) <- addEdge mutable from to
-      newFace <- addFaceBlock mutable 1
-      writeFace mutable outer 0
-      setCycle3 mutable newFace first second inner
-      linkEdges mutable oldPrevious outer
-      linkEdges mutable outer oldNext
-      writeFaceEdge mutable 0 outer
-      writeVertexOut mutable from outer
-      writeVertexOut mutable to inner
-      pure (Right outer)
+    Right () -> Right <$> closeOuterTurnWithCapacity mutable first
 {-# INLINE closeOuterTurn #-}
 
+-- | The circle-sweep form of 'closeOuterTurn'. Its capacity obstruction was
+-- discharged by 'reserveSweepCells'; topology mutation is otherwise identical
+-- to the checked public-internal operation used by joins.
+closeOuterTurnReserved
+  :: ReservedSweepCells s vertex directed undirected face
+  -> SweepCellCursor
+  -> Int
+  -> ST s (Int, SweepCellCursor)
+closeOuterTurnReserved
+  (ReservedSweepCells dense _)
+  (SweepCellCursor nextHalf nextFace)
+  first = do
+    denseInitializeUnconstrainedEdgeBlock dense nextHalf 1
+    replacement <- closeOuterTurnDenseAt dense nextHalf nextFace first
+    pure (replacement, SweepCellCursor (nextHalf + 2) (nextFace + 1))
+{-# INLINE closeOuterTurnReserved #-}
+
+-- | Materialize one turn closure at cells already owned by the caller's
+-- allocation section. Ordinary edits obtain those cells from the checked
+-- allocator; the reserved sweep obtains them from its immutable cursor.
+closeOuterTurnDenseAt
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Int
+  -> Int
+  -> Int
+  -> ST s Int
+closeOuterTurnDenseAt dense edgeBase newFace first = do
+  second <- denseReadNext dense first
+  oldPrevious <- denseReadPrevious dense first
+  oldNext <- denseReadNext dense second
+  from <- denseReadOrigin dense first
+  to <- denseReadOrigin dense (reverseIndex second)
+  let !outer = edgeBase
+      !inner = edgeBase + 1
+  denseWriteOrigin dense outer from
+  denseWriteOrigin dense inner to
+  denseWriteFace dense outer 0
+  -- @first -> second@ is the authoritative outer adjacency that licensed this
+  -- closure. Preserve those two already-correct cells and write only the six
+  -- changed links plus the new face section.
+  denseWriteNext dense second inner
+  denseWritePrevious dense inner second
+  denseWriteNext dense inner first
+  denseWritePrevious dense first inner
+  denseWriteFace dense first newFace
+  denseWriteFace dense second newFace
+  denseWriteFace dense inner newFace
+  denseWriteFaceEdge dense newFace first
+  denseLinkEdges dense oldPrevious outer
+  denseLinkEdges dense outer oldNext
+  denseWriteFaceEdge dense 0 outer
+  denseWriteVertexOut dense from outer
+  denseWriteVertexOut dense to inner
+  pure outer
+{-# INLINE closeOuterTurnDenseAt #-}
+
+closeOuterTurnWithCapacity
+  :: MutableDcel s vertex directed undirected face
+  -> Int
+  -> ST s Int
+closeOuterTurnWithCapacity mutable first = do
+  second <- readNext mutable first
+  oldPrevious <- readPrevious mutable first
+  oldNext <- readNext mutable second
+  from <- readOrigin mutable first
+  to <- readOrigin mutable (reverseIndex second)
+  (outer, inner) <- addEdge mutable from to
+  newFace <- addFaceBlock mutable 1
+  writeFace mutable outer 0
+  writeNext mutable second inner
+  writePrevious mutable inner second
+  writeNext mutable inner first
+  writePrevious mutable first inner
+  writeFace mutable first newFace
+  writeFace mutable second newFace
+  writeFace mutable inner newFace
+  writeFaceEdge mutable newFace first
+  linkEdges mutable oldPrevious outer
+  linkEdges mutable outer oldNext
+  writeFaceEdge mutable 0 outer
+  writeVertexOut mutable from outer
+  writeVertexOut mutable to inner
+  pure outer
+{-# INLINE closeOuterTurnWithCapacity #-}
+
 -- | Close every remaining left turn of the star-shaped sweep hull in one
 -- Graham-style pass. Each closure strictly decreases the outer-edge count, so
 -- the pass is linear in the visited hull plus the local Delaunay legalization
@@ -255,25 +471,25 @@
 -- when the interior is repaired. The pass counts its own closures and returns
 -- the drain's tallies; nothing is reported behind its back.
 fixHullConvexity
-  :: forall p s vertex directed undirected face
-   . KnownProbe p
-  => MutableDcel s vertex directed undirected face
+  :: ReservedSweepCells s vertex directed undirected face
+  -> SweepCellCursor
   -> OperationState s
-  -> ST s (Either BuildError (Int, Int, Int))
-fixHullConvexity mutable operation = do
-  start <- readFaceEdge mutable 0
+  -> LegalizationArena s
+  -> ST s (Either BuildError (Int, Int, Int, LegalizationArena s, SweepCellCursor))
+fixHullConvexity reserved@(ReservedSweepCells dense _) initialCursor operation initialArena = do
+  start <- denseReadFaceEdge dense 0
   if start < 0
-    then pure (Right (0, 0, 0))
+    then pure (Right (0, 0, 0, initialArena, initialCursor))
     else do
-      walked <- walk start start 0 0 0 0
+      walked <- walk start start 0 0 0 0 initialArena initialCursor
       case walked of
         Left obstruction -> pure (Left obstruction)
-        Right (top, closures) -> do
-          (flips, maxDepth) <- drainLegalization @p mutable operation top noStarVertex ValidMesh
-          pure (Right (closures, flips, maxDepth))
+        Right (top, closures, seededArena, finalCursor) -> do
+          LegalizationDrain flips maxDepth () finalArena <-
+            drainDenseUnconstrainedGenericLegalization dense seededArena top
+          pure (Right (closures, flips, maxDepth, finalArena, finalCursor))
  where
-  walk !start !current !stackSize !steps !top !closures = do
-    halfEdges <- directedEdgeCount mutable
+  walk !start !current !stackSize !steps !top !closures !arena cursor@(SweepCellCursor halfEdges _) = do
     if steps > halfEdges + 2
       then
         pure
@@ -285,44 +501,41 @@
               )
           )
       else do
-        following <- readNext mutable current
+        following <- denseReadNext dense current
         writeScratch operation stackSize current
-        reduction <- reduce (stackSize + 1) top closures
+        reduction <- reduce (stackSize + 1) top closures arena cursor
         case reduction of
           Left obstruction -> pure (Left obstruction)
-          Right (reduced, nextTop, nextClosures) -> do
+          Right (reduced, nextTop, nextClosures, nextArena, nextCursor) -> do
             finished <-
               if reduced < 2
                 then pure False
                 else (== following) <$> readScratch operation 1
             if finished
-              then pure (Right (nextTop, nextClosures))
-              else walk start following reduced (steps + 1) nextTop nextClosures
+              then pure (Right (nextTop, nextClosures, nextArena, nextCursor))
+              else walk start following reduced (steps + 1) nextTop nextClosures nextArena nextCursor
 
-  reduce !count !top !closures
-    | count < 2 = pure (Right (count, top, closures))
+  reduce !count !top !closures !arena !cursor
+    | count < 2 = pure (Right (count, top, closures, arena, cursor))
     | otherwise = do
         first <- readScratch operation (count - 2)
         second <- readScratch operation (count - 1)
-        fromVertex <- readOrigin mutable first
-        middleVertex <- readOrigin mutable (reverseIndex first)
-        targetVertex <- readOrigin mutable (reverseIndex second)
-        fromX <- readPointX mutable fromVertex
-        fromY <- readPointY mutable fromVertex
-        middleX <- readPointX mutable middleVertex
-        middleY <- readPointY mutable middleVertex
-        targetX <- readPointX mutable targetVertex
-        targetY <- readPointY mutable targetVertex
+        fromVertex <- denseReadOrigin dense first
+        middleVertex <- denseReadOrigin dense (reverseIndex first)
+        targetVertex <- denseReadOrigin dense (reverseIndex second)
+        fromX <- denseReadPointX dense fromVertex
+        fromY <- denseReadPointY dense fromVertex
+        middleX <- denseReadPointX dense middleVertex
+        middleY <- denseReadPointY dense middleVertex
+        targetX <- denseReadPointX dense targetVertex
+        targetY <- denseReadPointY dense targetVertex
         if orient2dCoordinates fromX fromY middleX middleY targetX targetY == GT
           then do
-            closed <- closeOuterTurn mutable first
-            case closed of
-              Left obstruction -> pure (Left obstruction)
-              Right replacement -> do
-                writeScratch operation (count - 2) replacement
-                nextTop <- seedGenericEdges operation top [first, second]
-                reduce (count - 1) nextTop (closures + 1)
-          else pure (Right (count, top, closures))
+            (replacement, nextCursor) <- closeOuterTurnReserved reserved cursor first
+            writeScratch operation (count - 2) replacement
+            (nextArena, nextTop) <- seedGenericPairInArena arena top first second
+            reduce (count - 1) nextTop (closures + 1) nextArena nextCursor
+          else pure (Right (count, top, closures, arena, cursor))
 
 visibleOuter :: MutableDcel s vertex directed undirected face -> Int -> Point -> ST s Bool
 visibleOuter mutable edge query = do
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Legalize.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Legalize.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Legalize.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Legalize.hs
@@ -4,10 +4,13 @@
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}
 
 -- | The seeding entry points that drive one legalization epoch.
 module Moonlight.Triangulation.Internal.DcelOperations.Legalize
   ( legalizeScratch
+  , legalizeStarEdge
+  , legalizeDenseStarEdgeInArena
   , legalizeEdges
   , legalizeCavityFanScratch
   ) where
@@ -17,17 +20,25 @@
 import Data.Foldable (traverse_)
 import qualified Data.Vector.Unboxed.Mutable as MUV
 import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
-  ( genericCandidate
+  ( CandidateDiscipline (..)
   , growLegalizationArena
-  , noStarVertex
   , seedGenericEdges
   , seedStarScratch
   )
 import Moonlight.Triangulation.Internal.DcelOperations.FlipRule (LegalizationLaw (..))
-import Moonlight.Triangulation.Internal.DcelOperations.Normalize (drainLegalization)
-import Moonlight.Triangulation.Internal.Mutable (MutableDcel)
+import Moonlight.Triangulation.Internal.DcelOperations.Normalize
+  ( LegalizationDrain
+  , drainDenseUnconstrainedStarLegalization
+  , drainLegalization
+  )
+import Moonlight.Triangulation.Internal.Mutable
+  ( DenseMutableDcel
+  , MutableDcel
+  , MutableTopology
+  )
 import Moonlight.Triangulation.Internal.OperationState
   ( Counter (..)
+  , LegalizationArena (..)
   , OperationState
   , addCounter
   , legalizationArena
@@ -41,7 +52,7 @@
 legalizeEdges :: MutableDcel s vertex directed undirected face -> OperationState s -> [Int] -> ST s ()
 legalizeEdges mutable operation initial = do
   top <- seedGenericEdges operation 0 initial
-  (flips, maxDepth) <- drainLegalization @'ProbeOff mutable operation top noStarVertex ValidMesh
+  (flips, maxDepth) <- drainLegalization @'ProbeOff mutable operation top GenericCandidates ValidMesh
   addCounter operation CounterEdgeFlips flips
   maxCounter operation CounterLegalizationMaxStack maxDepth
 
@@ -64,11 +75,12 @@
 legalizeCavityFanScratch mutable operation cavityFloor scratchOffset candidateCount = do
   initialArena <- legalizationArena operation
   arena <- growLegalizationArena initialArena candidateCount
-  when (MUV.length arena /= MUV.length initialArena) (storeLegalizationArena operation arena)
+  when (legalizationArenaLength arena /= legalizationArenaLength initialArena) (storeLegalizationArena operation arena)
+  let LegalizationArena values = arena
   traverse_
     (\index -> do
        edge <- readScratch operation (scratchOffset + index)
-       MUV.unsafeWrite arena index (packIndex (genericCandidate edge))
+       MUV.unsafeWrite values index (packIndex edge)
     )
     [0 .. candidateCount - 1]
   (flips, maxDepth) <-
@@ -77,7 +89,7 @@
       mutable
       operation
       candidateCount
-      noStarVertex
+      GenericCandidates
       (CavityRepair cavityFloor)
   addCounter operation CounterEdgeFlips flips
   maxCounter operation CounterLegalizationMaxStack maxDepth
@@ -85,6 +97,44 @@
 legalizeScratch :: forall p s vertex directed undirected face. KnownProbe p => MutableDcel s vertex directed undirected face -> OperationState s -> Int -> Int -> ST s ()
 legalizeScratch mutable operation vertex candidateCount = do
   top <- seedStarScratch operation 0 candidateCount
-  (flips, maxDepth) <- drainLegalization @p mutable operation top vertex ValidMesh
+  (flips, maxDepth) <- drainLegalization @p mutable operation top (StarCandidates vertex) ValidMesh
   addCounter operation CounterEdgeFlips flips
   maxCounter operation CounterLegalizationMaxStack maxDepth
+
+-- | Normalize one newly covered outer edge against the inserted star vertex.
+-- The circle sweep always covers exactly one edge; routing that singleton
+-- through the shared scratch section merely materializes a one-element range
+-- before immediately copying it into this arena. This is the same star-law
+-- section written at its actual arity.
+legalizeStarEdge
+  :: forall p mutable s vertex directed undirected face
+   . (KnownProbe p, MutableTopology mutable)
+  => mutable s vertex directed undirected face
+  -> OperationState s
+  -> Int
+  -> Int
+  -> ST s (Int, Int)
+legalizeStarEdge mutable operation vertex edge = do
+  arena <- legalizationArena operation
+  let LegalizationArena values = arena
+  MUV.unsafeWrite values 0 (packIndex edge)
+  drainLegalization @p mutable operation 1 (StarCandidates vertex) ValidMesh
+{-# INLINE legalizeStarEdge #-}
+
+-- | The monomorphic fresh-build interpreter for the singleton star epoch,
+-- consuming and returning the sweep's borrowed candidate section.
+legalizeDenseStarEdgeInArena
+  :: DenseMutableDcel s vertex directed undirected face
+  -> LegalizationArena s
+  -> Int
+  -> Int
+  -> ST s (LegalizationDrain s ())
+legalizeDenseStarEdgeInArena dense arena vertex edge = do
+  let LegalizationArena values = arena
+  MUV.unsafeWrite values 0 (packIndex edge)
+  drainDenseUnconstrainedStarLegalization dense arena 1 vertex
+{-# NOINLINE legalizeDenseStarEdgeInArena #-}
+
+legalizationArenaLength :: LegalizationArena s -> Int
+legalizationArenaLength (LegalizationArena values) = MUV.length values
+{-# INLINE legalizationArenaLength #-}
diff --git a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Normalize.hs b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Normalize.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Normalize.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Normalize.hs
@@ -4,47 +4,62 @@
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}
 
 -- | The normalization procedure of the flip rewrite system.
 module Moonlight.Triangulation.Internal.DcelOperations.Normalize
   ( drainLegalization
+  , LegalizationDrain (..)
+  , drainDenseUnconstrainedStarLegalization
+  , drainDenseUnconstrainedGenericLegalization
   ) where
 
 import Control.Monad (when)
 import Control.Monad.ST (ST)
-import Data.Bits ((.&.), shiftR)
+import Data.Bits (shiftR)
 import Data.STRef (readSTRef)
 import qualified Data.Vector.Unboxed.Mutable as MUV
+import Data.Word (Word32)
 import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
-  ( genericCandidate
+  ( CandidateDiscipline (..)
   , growLegalizationArena
-  , starCandidate
   )
 import Moonlight.Triangulation.Internal.DcelOperations.FlipRewrite (applyFlip)
 import Moonlight.Triangulation.Internal.DcelOperations.FlipRule
   ( LegalizationLaw (..)
   , diagonalFires
+  , illegalDiagonal
   )
 import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex)
 import Moonlight.Triangulation.Internal.Mutable
-  ( MutableDcel (..)
-  , readConstraint
-  , readFace
-  , readNext
-  , readOrigin
-  , readPointX
-  , readPointY
-  , readPrevious
+  ( DenseMutableDcel
+  , MutableDcel (..)
+  , MutableTopology (..)
   )
 import Moonlight.Triangulation.Internal.OperationState
   ( Counter (..)
+  , LegalizationArena (..)
   , OperationState
   , legalizationArena
   , storeLegalizationArena
   )
 import Moonlight.Triangulation.Internal.PackedIndex (packIndex)
-import Moonlight.Triangulation.Internal.Probe (KnownProbe (..))
+import Moonlight.Triangulation.Internal.Probe
+  ( KnownProbe (..)
+  , Probe (..)
+  , ProbeCounter
+  )
 
+-- | The authoritative result of one normalization section. The arena is part
+-- of the result because an adversarial frontier may grow it; callers that
+-- borrow the section for several local rewrites can therefore compose those
+-- rewrites without bouncing through the operation's reference between them.
+data LegalizationDrain s counter = LegalizationDrain
+  {-# UNPACK #-} !Int
+  {-# UNPACK #-} !Int
+  !counter
+  !(LegalizationArena s)
+
 -- | The normalization procedure of a confluent terminating rewrite system, and
 -- one canonical legalization engine because a normalization procedure is what
 -- it is.
@@ -85,27 +100,51 @@
 -- records, and the apex the turn looks for is the apex the judgement needs, so
 -- one pass over the quadrilateral answers all three.
 drainLegalization
-  :: forall p s vertex directed undirected face
-   . KnownProbe p
-  => MutableDcel s vertex directed undirected face
+  :: forall p mutable s vertex directed undirected face
+   . (KnownProbe p, MutableTopology mutable)
+  => mutable s vertex directed undirected face
   -> OperationState s
   -> Int
-  -> Int
+  -> CandidateDiscipline
   -> LegalizationLaw
   -> ST s (Int, Int)
-drainLegalization mutable operation seededTop starVertex law = do
+drainLegalization topology operation seededTop discipline law = do
+  let !mutable = topologyOwner topology
   -- No constraint can appear during a drain, so a mesh holding none at entry
   -- never needs the per-candidate protection read.
   constrained <- readSTRef (mdConstraintCount mutable)
   initialArena <- legalizationArena operation
-  -- A star candidate is turned so that the inserted vertex is the apex
-  -- opposite the diagonal, so that apex is the same vertex on every star
-  -- candidate the drain pops and its two coordinates are read once here
-  -- instead of once per candidate.
-  starX <- if starVertex < 0 then pure 0 else readPointX mutable starVertex
-  starY <- if starVertex < 0 then pure 0 else readPointY mutable starVertex
-  let !guarded = constrained /= 0
-      -- Everything the cavity fan did not create is pinned. The border loop's
+  LegalizationDrain flips maxTop candidates finalArena <-
+    drainLegalizationInArena
+      @p
+      topology
+      initialArena
+      (constrained /= 0)
+      seededTop
+      discipline
+      law
+  when (legalizationArenaLength finalArena /= legalizationArenaLength initialArena) $
+    storeLegalizationArena operation finalArena
+  probeCharge @p operation CounterDiagLegalizationCandidates candidates
+  pure (flips, maxTop)
+{-# INLINE drainLegalization #-}
+
+-- | Interpret the single normalization law in an explicitly borrowed arena.
+-- The boolean is the already-established constraint obstruction: a fresh
+-- unconstrained build passes 'False', while the general entry derives it once
+-- from the mutable owner above.
+drainLegalizationInArena
+  :: forall p mutable s vertex directed undirected face
+   . (KnownProbe p, MutableTopology mutable)
+  => mutable s vertex directed undirected face
+  -> LegalizationArena s
+  -> Bool
+  -> Int
+  -> CandidateDiscipline
+  -> LegalizationLaw
+  -> ST s (LegalizationDrain s (ProbeCounter p))
+drainLegalizationInArena topology (LegalizationArena initialArena) guarded seededTop discipline law = do
+  let -- Everything the cavity fan did not create is pinned. The border loop's
       -- legality is already decided by the outside triangle it keeps, and
       -- testing it against a neighbourhood that is still inverted could only
       -- produce a spurious verdict. 'ValidMesh' pins nothing, which is the
@@ -114,109 +153,201 @@
         ValidMesh -> 0
         CavityRepair floorEdge -> floorEdge
 
-      loop !arena !top !maxTop !flips !candidates
+      eligible rawEdge
+        | rawEdge `shiftR` 1 < floorPair = pure False
+        | guarded = not <$> topologyReadConstraint topology rawEdge
+        | otherwise = pure True
+
+      loopStar !starVertex !starX !starY !arena !top !maxTop !flips !candidates
         | top <= 0 = pure (flips, maxTop, candidates, arena)
         | otherwise = do
             let !nextTop = top - 1
             packedWord <- MUV.unsafeRead arena nextTop
-            let !packed = fromIntegral packedWord :: Int
-                !rawEdge = packed `shiftR` 1
-                !isStar = packed .&. 1 == 0
-            eligible <-
-              if rawEdge `shiftR` 1 < floorPair
-                then pure False
-                else if guarded then not <$> readConstraint mutable rawEdge else pure True
-            if not eligible
-              then loop arena nextTop maxTop flips (probeBump @p candidates)
+            let !rawEdge = fromIntegral packedWord :: Int
+            mayFire <- eligible rawEdge
+            if not mayFire
+              then loopStar starVertex starX starY arena nextTop maxTop flips (probeBump @p candidates)
               else do
                 let !rawTwin = reverseIndex rawEdge
-                rawFace <- readFace mutable rawEdge
-                rawTwinFace <- readFace mutable rawTwin
+                rawFace <- topologyReadFace topology rawEdge
+                rawTwinFace <- topologyReadFace topology rawTwin
                 if rawFace == 0 || rawTwinFace == 0
-                  then loop arena nextTop maxTop flips (probeBump @p candidates)
+                  then loopStar starVertex starX starY arena nextTop maxTop flips (probeBump @p candidates)
                   else do
-                    rawBefore <- readPrevious mutable rawEdge
-                    rawTwinBefore <- readPrevious mutable rawTwin
-                    rawApex <- readOrigin mutable rawBefore
-                    rawTwinApex <- readOrigin mutable rawTwinBefore
-                    -- A star candidate names an undirected edge and has to be
-                    -- turned so the inserted vertex is the apex opposite it; a
-                    -- generic one already names the direction to test. Both
-                    -- want the same two apexes, so the one read that settles
-                    -- the turn is also the one that supplies the quadrilateral.
-                    let !turned = isStar && rawApex /= starVertex
-                    if turned && rawTwinApex /= starVertex
-                      then loop arena nextTop maxTop flips (probeBump @p candidates)
+                    rawBefore <- topologyReadPrevious topology rawEdge
+                    rawTwinBefore <- topologyReadPrevious topology rawTwin
+                    rawTwinApex <- topologyReadOrigin topology rawTwinBefore
+                    a <- topologyReadOrigin topology rawEdge
+                    b <- topologyReadOrigin topology rawTwin
+                    ax <- topologyReadPointX topology a
+                    ay <- topologyReadPointY topology a
+                    bx <- topologyReadPointX topology b
+                    by <- topologyReadPointY topology b
+                    dx <- topologyReadPointX topology rawTwinApex
+                    dy <- topologyReadPointY topology rawTwinApex
+                    -- A star candidate is oriented against the inserted apex,
+                    -- exactly the premise used by the ordinary insertion
+                    -- legalizer. The two convexity predicates in the generic
+                    -- valid-mesh law merely re-prove that premise for every
+                    -- pop; the in-circle rule alone owns this section.
+                    if not (illegalDiagonal ax ay bx by starX starY dx dy)
+                      then loopStar starVertex starX starY arena nextTop maxTop flips (probeBump @p candidates)
                       else do
-                        let !edge = if turned then rawTwin else rawEdge
-                            !twin = if turned then rawEdge else rawTwin
-                            !leftFace = if turned then rawTwinFace else rawFace
-                            !rightFace = if turned then rawFace else rawTwinFace
-                            !edgePrevious = if turned then rawTwinBefore else rawBefore
-                            !twinPrevious = if turned then rawBefore else rawTwinBefore
-                            !c = if turned then rawTwinApex else rawApex
-                            !d = if turned then rawApex else rawTwinApex
-                        a <- readOrigin mutable edge
-                        b <- readOrigin mutable twin
-                        ax <- readPointX mutable a
-                        ay <- readPointY mutable a
-                        bx <- readPointX mutable b
-                        by <- readPointY mutable b
-                        cx <- if isStar then pure starX else readPointX mutable c
-                        cy <- if isStar then pure starY else readPointY mutable c
-                        dx <- readPointX mutable d
-                        dy <- readPointY mutable d
-                        if not (diagonalFires law ax ay bx by cx cy dx dy)
-                          then loop arena nextTop maxTop flips (probeBump @p candidates)
-                          else do
-                            -- The neighbourhood is complete before the rewrite
-                            -- consumes it, and the pushes carry the same edges
-                            -- in the same order they always did.
-                            edgeNext <- readNext mutable edge
-                            twinNext <- readNext mutable twin
-                            applyFlip
-                              mutable
-                              edge
-                              twin
-                              edgeNext
-                              edgePrevious
-                              twinNext
-                              twinPrevious
-                              leftFace
-                              rightFace
-                              a
-                              b
-                              c
-                              d
-                            if isStar
-                              then do
-                                let !addedTop = nextTop + 2
-                                grown <- growLegalizationArena arena addedTop
-                                MUV.unsafeWrite grown nextTop (packIndex (starCandidate twinPrevious))
-                                MUV.unsafeWrite grown (nextTop + 1) (packIndex (starCandidate twinNext))
-                                loop
-                                  grown
-                                  addedTop
-                                  (max maxTop addedTop)
-                                  (flips + 1)
-                                  (probeBump @p candidates)
-                              else do
-                                let !addedTop = nextTop + 4
-                                grown <- growLegalizationArena arena addedTop
-                                MUV.unsafeWrite grown nextTop (packIndex (genericCandidate edgeNext))
-                                MUV.unsafeWrite grown (nextTop + 1) (packIndex (genericCandidate edgePrevious))
-                                MUV.unsafeWrite grown (nextTop + 2) (packIndex (genericCandidate twinNext))
-                                MUV.unsafeWrite grown (nextTop + 3) (packIndex (genericCandidate twinPrevious))
-                                loop
-                                  grown
-                                  addedTop
-                                  (max maxTop addedTop)
-                                  (flips + 1)
-                                  (probeBump @p candidates)
-  (flips, maxTop, candidates, finalArena) <-
-    loop initialArena seededTop seededTop 0 (probeZero @p)
-  when (MUV.length finalArena /= MUV.length initialArena) $
-    storeLegalizationArena operation finalArena
-  probeCharge @p operation CounterDiagLegalizationCandidates candidates
-  pure (flips, maxTop)
-{-# INLINE drainLegalization #-}
+                        -- Star seeding is directional: the inserted vertex is
+                        -- the previous-origin apex of every candidate. A flip
+                        -- preserves that proof for precisely these two
+                        -- directed neighbours, so neither an undirected tag nor
+                        -- a rediscovery read belongs in this epoch.
+                        rawNext <- topologyReadNext topology rawEdge
+                        rawTwinNext <- topologyReadNext topology rawTwin
+                        applyFlip
+                          topology
+                          rawEdge
+                          rawTwin
+                          rawNext
+                          rawBefore
+                          rawTwinNext
+                          rawTwinBefore
+                          rawFace
+                          rawTwinFace
+                          a
+                          b
+                          starVertex
+                          rawTwinApex
+                        let !addedTop = nextTop + 2
+                        grown <- growValues arena addedTop
+                        MUV.unsafeWrite grown nextTop (packIndex rawTwinBefore)
+                        MUV.unsafeWrite grown (nextTop + 1) (packIndex rawTwinNext)
+                        loopStar
+                          starVertex
+                          starX
+                          starY
+                          grown
+                          addedTop
+                          (max maxTop addedTop)
+                          (flips + 1)
+                          (probeBump @p candidates)
+
+      loopGeneric !arena !top !maxTop !flips !candidates
+        | top <= 0 = pure (flips, maxTop, candidates, arena)
+        | otherwise = do
+            let !nextTop = top - 1
+            packedWord <- MUV.unsafeRead arena nextTop
+            let !edge = fromIntegral packedWord :: Int
+            mayFire <- eligible edge
+            if not mayFire
+              then loopGeneric arena nextTop maxTop flips (probeBump @p candidates)
+              else do
+                let !twin = reverseIndex edge
+                leftFace <- topologyReadFace topology edge
+                rightFace <- topologyReadFace topology twin
+                if leftFace == 0 || rightFace == 0
+                  then loopGeneric arena nextTop maxTop flips (probeBump @p candidates)
+                  else do
+                    edgePrevious <- topologyReadPrevious topology edge
+                    twinPrevious <- topologyReadPrevious topology twin
+                    c <- topologyReadOrigin topology edgePrevious
+                    d <- topologyReadOrigin topology twinPrevious
+                    a <- topologyReadOrigin topology edge
+                    b <- topologyReadOrigin topology twin
+                    ax <- topologyReadPointX topology a
+                    ay <- topologyReadPointY topology a
+                    bx <- topologyReadPointX topology b
+                    by <- topologyReadPointY topology b
+                    cx <- topologyReadPointX topology c
+                    cy <- topologyReadPointY topology c
+                    dx <- topologyReadPointX topology d
+                    dy <- topologyReadPointY topology d
+                    if not (diagonalFires law ax ay bx by cx cy dx dy)
+                      then loopGeneric arena nextTop maxTop flips (probeBump @p candidates)
+                      else do
+                        edgeNext <- topologyReadNext topology edge
+                        twinNext <- topologyReadNext topology twin
+                        applyFlip
+                          topology
+                          edge
+                          twin
+                          edgeNext
+                          edgePrevious
+                          twinNext
+                          twinPrevious
+                          leftFace
+                          rightFace
+                          a
+                          b
+                          c
+                          d
+                        let !addedTop = nextTop + 4
+                        grown <- growValues arena addedTop
+                        MUV.unsafeWrite grown nextTop (packIndex edgeNext)
+                        MUV.unsafeWrite grown (nextTop + 1) (packIndex edgePrevious)
+                        MUV.unsafeWrite grown (nextTop + 2) (packIndex twinNext)
+                        MUV.unsafeWrite grown (nextTop + 3) (packIndex twinPrevious)
+                        loopGeneric
+                          grown
+                          addedTop
+                          (max maxTop addedTop)
+                          (flips + 1)
+                          (probeBump @p candidates)
+  drained <-
+    case discipline of
+      StarCandidates starVertex -> do
+        starX <- topologyReadPointX topology starVertex
+        starY <- topologyReadPointY topology starVertex
+        loopStar starVertex starX starY initialArena seededTop seededTop 0 (probeZero @p)
+      GenericCandidates ->
+        loopGeneric initialArena seededTop seededTop 0 (probeZero @p)
+  let (!flips, !maxTop, !candidates, !finalArena) = drained
+  pure (LegalizationDrain flips maxTop candidates (LegalizationArena finalArena))
+ where
+  growValues :: MUV.MVector s Word32 -> Int -> ST s (MUV.MVector s Word32)
+  growValues values required = do
+    LegalizationArena grown <-
+      growLegalizationArena (LegalizationArena values) required
+    pure grown
+{-# INLINE drainLegalizationInArena #-}
+
+-- | The dense, fresh-build interpreter for a star epoch. Fresh construction
+-- proves the absence of constrained edges; the borrowed arena is returned so
+-- the surrounding sweep can glue several epochs before restoring operation
+-- ownership.
+drainDenseUnconstrainedStarLegalization
+  :: DenseMutableDcel s vertex directed undirected face
+  -> LegalizationArena s
+  -> Int
+  -> Int
+  -> ST s (LegalizationDrain s ())
+drainDenseUnconstrainedStarLegalization dense arena top starVertex =
+  drainLegalizationInArena
+    @'ProbeOff
+    dense
+    arena
+    False
+    top
+    (StarCandidates starVertex)
+    ValidMesh
+{-# INLINE drainDenseUnconstrainedStarLegalization #-}
+
+-- | The matching dense interpreter for a generic legalization epoch. Keeping
+-- both monomorphic boundaries out of the circle-sweep worker prevents the
+-- normalizer and flip rewrite from being copied into every insertion
+-- continuation; both still descend through 'drainLegalizationInArena'.
+drainDenseUnconstrainedGenericLegalization
+  :: DenseMutableDcel s vertex directed undirected face
+  -> LegalizationArena s
+  -> Int
+  -> ST s (LegalizationDrain s ())
+drainDenseUnconstrainedGenericLegalization dense arena top =
+  drainLegalizationInArena
+    @'ProbeOff
+    dense
+    arena
+    False
+    top
+    GenericCandidates
+    ValidMesh
+{-# INLINE drainDenseUnconstrainedGenericLegalization #-}
+
+legalizationArenaLength :: LegalizationArena s -> Int
+legalizationArenaLength (LegalizationArena values) = MUV.length values
+{-# INLINE legalizationArenaLength #-}
diff --git a/src-dcel/Moonlight/Triangulation/Internal/ExactSegmentEvents.hs b/src-dcel/Moonlight/Triangulation/Internal/ExactSegmentEvents.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/ExactSegmentEvents.hs
@@ -0,0 +1,889 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | One exact owner for non-disjoint relations and split points in a finite
+-- segment family. Collinear intervals descend by supporting-line sections;
+-- non-collinear intersections descend through an immutable Bentley--Ottmann
+-- status tree. Callers attach provenance only after this geometry glues.
+module Moonlight.Triangulation.Internal.ExactSegmentEvents
+  ( ExactSweepSegmentId (..)
+  , ExactSegmentEvent (..)
+  , ExactSegmentEventObstruction (..)
+  , ExactSegmentEventPlan
+  , exactSegmentEventPlan
+  , exactSegmentEvents
+  , exactSegmentSplitPoints
+  , exactSegmentRelationMap
+  , exactSegmentPairChecks
+  , exactSegmentSweepMaximumHeight
+  ) where
+
+import Control.DeepSeq (NFData)
+import Control.Applicative ((<|>))
+import Control.Monad (filterM, foldM)
+import Data.List (sortBy)
+import qualified Data.IntMap.Strict as IntMap
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict (Map)
+import qualified Data.Set as Set
+import Data.Set (Set)
+import qualified Data.Vector as V
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Exact
+  ( ExactIntersectionError
+  , ExactPoint
+  , ExactSegment
+  , SegmentRelation (..)
+  , exactOnClosedSegment
+  , exactPointCoordinates
+  , exactSegmentEndpoints
+  , exactSegmentRelation
+  , exactSupportingLineIntersection
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle
+  ( orderedPair
+  , unorderedPairs
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational
+  , exactRationalDenominator
+  , exactRationalNumerator
+  )
+
+newtype ExactSweepSegmentId = ExactSweepSegmentId Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data ExactSegmentEvent
+  = ExactProperCrossing !ExactSweepSegmentId !ExactSweepSegmentId !ExactPoint
+  | ExactEndpointTouch !ExactSweepSegmentId !ExactSweepSegmentId !ExactPoint
+  | ExactSharedEndpoint !ExactSweepSegmentId !ExactSweepSegmentId !ExactPoint
+  | ExactDuplicateSegments !ExactSweepSegmentId !ExactSweepSegmentId
+  | ExactCollinearOverlap
+      !ExactSweepSegmentId
+      !ExactSweepSegmentId
+      !ExactPoint
+      !ExactPoint
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data ExactSegmentEventObstruction
+  = ExactSweepIntersectionObstruction
+      !ExactSweepSegmentId
+      !ExactSweepSegmentId
+      !ExactIntersectionError
+  | ExactSweepRelationWitnessMissing
+      !ExactSweepSegmentId
+      !ExactSweepSegmentId
+      !SegmentRelation
+  | ExactSweepSegmentMissing !ExactSweepSegmentId
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+data ExactSegmentEventPlan = ExactSegmentEventPlan
+  { plannedEvents :: !(Map (ExactSweepSegmentId, ExactSweepSegmentId) ExactSegmentEvent)
+  , plannedSplitPoints :: !(V.Vector [ExactPoint])
+  , plannedPairChecks :: !Int
+  , plannedMaximumHeight :: !Int
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+data SegmentMeta = SegmentMeta
+  { metaId :: !ExactSweepSegmentId
+  , metaSegment :: !ExactSegment
+  , metaLow :: !ExactPoint
+  , metaHigh :: !ExactPoint
+  , metaVertical :: !Bool
+  }
+
+data SupportingLine = SupportingLine !Integer !Integer !Integer
+  deriving stock (Eq, Ord, Show)
+
+data EventBundle = EventBundle
+  { eventStarts :: !(Set ExactSweepSegmentId)
+  , eventEnds :: !(Set ExactSweepSegmentId)
+  , eventScheduled :: !(Set (ExactSweepSegmentId, ExactSweepSegmentId))
+  }
+
+emptyEventBundle :: EventBundle
+emptyEventBundle = EventBundle Set.empty Set.empty Set.empty
+
+mergeEventBundle :: EventBundle -> EventBundle -> EventBundle
+mergeEventBundle left right =
+  EventBundle
+    { eventStarts = eventStarts left <> eventStarts right
+    , eventEnds = eventEnds left <> eventEnds right
+    , eventScheduled = eventScheduled left <> eventScheduled right
+    }
+
+data EventAccumulation = EventAccumulation
+  { accumulatedEvents :: !(Map (ExactSweepSegmentId, ExactSweepSegmentId) ExactSegmentEvent)
+  , accumulatedSplits :: !(IntMap.IntMap (Set ExactPoint))
+  , accumulatedPairChecks :: !Int
+  , accumulatedMaximumHeight :: !Int
+  }
+
+data StatusTree
+  = StatusEmpty
+  | StatusNode !Int !StatusTree !SegmentMeta !StatusTree
+
+data PointChange = PointChange
+  { changedPoint :: !ExactPoint
+  , changedContinuing :: !(Set ExactSweepSegmentId)
+  }
+
+data StatusSide = StatusBefore | StatusAfter
+  deriving stock (Eq)
+
+-- | Build the complete exact event plan. The private sweep reports its maximum
+-- AVL height and relation checks so benchmarks can distinguish output growth
+-- from residual orchestration.
+exactSegmentEventPlan
+  :: V.Vector ExactSegment
+  -> Either ExactSegmentEventObstruction ExactSegmentEventPlan
+exactSegmentEventPlan segments = do
+  let metas = V.imap segmentMeta segments
+      initialAccumulation =
+        EventAccumulation
+          { accumulatedEvents = Map.empty
+          , accumulatedSplits = IntMap.empty
+          , accumulatedPairChecks = 0
+          , accumulatedMaximumHeight = 0
+          }
+  let lineGroups = supportingLineGroups metas
+  afterCollinear <- foldM recordCollinearGroup initialAccumulation (Map.elems lineGroups)
+  let initialQueue = V.foldl' insertEndpointEvents Map.empty metas
+  completed <- sweep metas initialQueue StatusEmpty afterCollinear
+  pure
+    ExactSegmentEventPlan
+      { plannedEvents = accumulatedEvents completed
+      , plannedSplitPoints = finalizeSplitPoints segments (accumulatedSplits completed)
+      , plannedPairChecks = accumulatedPairChecks completed
+      , plannedMaximumHeight = accumulatedMaximumHeight completed
+      }
+
+finalizeSplitPoints
+  :: V.Vector ExactSegment
+  -> IntMap.IntMap (Set ExactPoint)
+  -> V.Vector [ExactPoint]
+finalizeSplitPoints segments splitPoints =
+  V.imap
+    (\index segment ->
+       let (from, to) = exactSegmentEndpoints segment
+           eventPoints = IntMap.findWithDefault Set.empty index splitPoints
+        in sortAlong segment (Set.toList (Set.insert from (Set.insert to eventPoints))))
+    segments
+
+segmentMeta :: Int -> ExactSegment -> SegmentMeta
+segmentMeta index segment =
+  let (firstPoint, secondPoint) = exactSegmentEndpoints segment
+      low = min firstPoint secondPoint
+      high = max firstPoint secondPoint
+      (lowX, _) = exactPointCoordinates low
+      (highX, _) = exactPointCoordinates high
+   in SegmentMeta
+        { metaId = ExactSweepSegmentId index
+        , metaSegment = segment
+        , metaLow = low
+        , metaHigh = high
+        , metaVertical = lowX == highX
+        }
+
+supportingLineGroups
+  :: V.Vector SegmentMeta
+  -> Map SupportingLine [SegmentMeta]
+supportingLineGroups =
+  V.foldl'
+    (\groups meta -> Map.insertWith (<>) (supportingLine meta) [meta] groups)
+    Map.empty
+
+supportingLine :: SegmentMeta -> SupportingLine
+supportingLine meta =
+  let (from, to) = exactSegmentEndpoints (metaSegment meta)
+      (fromX, fromY) = exactPointCoordinates from
+      (toX, toY) = exactPointCoordinates to
+      deltaX = toX - fromX
+      deltaY = toY - fromY
+      a = deltaY
+      b = negate deltaX
+      c = deltaX * fromY - deltaY * fromX
+      denominators = map exactRationalDenominator [a, b, c]
+      commonDenominator = foldl' lcm 1 denominators
+      integerCoefficient coefficient =
+        exactRationalNumerator coefficient
+          * (commonDenominator `quot` exactRationalDenominator coefficient)
+      integerA = integerCoefficient a
+      integerB = integerCoefficient b
+      integerC = integerCoefficient c
+      commonDivisor = gcd (abs integerA) (gcd (abs integerB) (abs integerC))
+      sign
+        | integerA < 0 = -1
+        | integerA == 0 && integerB < 0 = -1
+        | otherwise = 1
+      normalize coefficient = sign * (coefficient `quot` commonDivisor)
+   in SupportingLine (normalize integerA) (normalize integerB) (normalize integerC)
+
+recordCollinearGroup
+  :: EventAccumulation
+  -> [SegmentMeta]
+  -> Either ExactSegmentEventObstruction EventAccumulation
+recordCollinearGroup initial metas =
+  fst <$> foldM descend (initial, []) ordered
+ where
+  ordered = sortBy compareInterval metas
+  compareInterval left right =
+    compare (metaLow left, metaHigh left, metaId left) (metaLow right, metaHigh right, metaId right)
+  descend (accumulation, active) current = do
+    let retained = filter (\candidate -> metaHigh candidate >= metaLow current) active
+    updated <-
+      foldM
+        (\accumulated candidate -> recordRelation accumulated candidate current)
+        accumulation
+        retained
+    pure (updated, current : retained)
+
+insertEndpointEvents
+  :: Map ExactPoint EventBundle
+  -> SegmentMeta
+  -> Map ExactPoint EventBundle
+insertEndpointEvents queue meta =
+  insertBundle (metaHigh meta) (emptyEventBundle{eventEnds = Set.singleton (metaId meta)})
+    (insertBundle (metaLow meta) (emptyEventBundle{eventStarts = Set.singleton (metaId meta)}) queue)
+
+insertBundle
+  :: ExactPoint
+  -> EventBundle
+  -> Map ExactPoint EventBundle
+  -> Map ExactPoint EventBundle
+insertBundle = Map.insertWith mergeEventBundle
+
+sweep
+  :: V.Vector SegmentMeta
+  -> Map ExactPoint EventBundle
+  -> StatusTree
+  -> EventAccumulation
+  -> Either ExactSegmentEventObstruction EventAccumulation
+sweep metas queue status accumulation =
+  case Map.lookupMin queue of
+    Nothing -> Right accumulation
+    Just (firstPoint, _) -> do
+      let (currentX, _) = exactPointCoordinates firstPoint
+          (batch, laterQueue) =
+            Map.spanAntitone
+              (\point -> fst (exactPointCoordinates point) == currentX)
+              queue
+          startedIds =
+            Set.toAscList
+              (Map.foldl' (\ids bundle -> ids <> eventStarts bundle) Set.empty batch)
+      started <- traverse (requireMeta metas) startedIds
+      let startingIds = map metaId (filter (not . metaVertical) started)
+          verticalIds = map metaId (filter metaVertical started)
+      withVerticals <-
+        recordVerticalRelations
+          metas
+          currentX
+          startingIds
+          verticalIds
+          status
+          accumulation
+      (withEvents, removals, insertions, changes) <-
+        foldM
+          (processPoint metas status)
+          (withVerticals, Set.empty, Set.empty, [])
+          (Map.toAscList batch)
+      statusWithout <-
+        foldM
+          (deleteStatus metas StatusBefore currentX)
+          status
+          (Set.toAscList removals)
+      statusAfter <-
+        foldM
+          (insertStatus metas StatusAfter currentX)
+          statusWithout
+          (Set.toAscList insertions)
+      (scheduledQueue, scheduledAccumulation) <-
+        foldM
+          (scheduleAroundChange metas currentX statusAfter)
+          (laterQueue, withEvents)
+          changes
+      let measured =
+            scheduledAccumulation
+              { accumulatedMaximumHeight =
+                  max
+                    (accumulatedMaximumHeight scheduledAccumulation)
+                    (statusHeight statusAfter)
+              }
+      sweep metas scheduledQueue statusAfter measured
+
+recordVerticalRelations
+  :: V.Vector SegmentMeta
+  -> ExactRational
+  -> [ExactSweepSegmentId]
+  -> [ExactSweepSegmentId]
+  -> StatusTree
+  -> EventAccumulation
+  -> Either ExactSegmentEventObstruction EventAccumulation
+recordVerticalRelations metas currentX startingIds verticalIds status accumulation = do
+  temporaryStatus <-
+    foldM (insertStatus metas StatusAfter currentX) status startingIds
+  foldM (recordVertical temporaryStatus) accumulation verticalIds
+ where
+  recordVertical temporaryStatus accumulated verticalId = do
+    vertical <- requireMeta metas verticalId
+    let (_, lowY) = exactPointCoordinates (metaLow vertical)
+        (_, highY) = exactPointCoordinates (metaHigh vertical)
+        candidates = statusRangeByY currentX lowY highY temporaryStatus
+    foldM
+      (\current candidateId -> do
+         candidate <- requireMeta metas candidateId
+         recordRelation current vertical candidate)
+      accumulated
+      candidates
+
+processPoint
+  :: V.Vector SegmentMeta
+  -> StatusTree
+  -> ( EventAccumulation
+     , Set ExactSweepSegmentId
+     , Set ExactSweepSegmentId
+     , [PointChange]
+     )
+  -> (ExactPoint, EventBundle)
+  -> Either
+      ExactSegmentEventObstruction
+      ( EventAccumulation
+      , Set ExactSweepSegmentId
+      , Set ExactSweepSegmentId
+      , [PointChange]
+      )
+processPoint metas status (accumulation, removals, insertions, changes) (point, bundle) = do
+  let (x, y) = exactPointCoordinates point
+      activeAtPoint = Set.fromList (statusAtY x y status)
+      scheduledIds =
+        Set.fromList
+          [ segmentId
+          | (leftId, rightId) <- Set.toList (eventScheduled bundle)
+          , segmentId <- [leftId, rightId]
+          ]
+      candidates = eventStarts bundle <> eventEnds bundle <> activeAtPoint <> scheduledIds
+  incident <-
+    Set.fromList
+      <$> filterM
+        (\segmentId -> do
+           meta <- requireMeta metas segmentId
+           let (from, to) = exactSegmentEndpoints (metaSegment meta)
+           pure (exactOnClosedSegment from to point))
+        (Set.toAscList candidates)
+  withRelations <- recordIncidentPairs metas accumulation incident
+  metasAtPoint <- traverse (requireMeta metas) (Set.toAscList incident)
+  let removable =
+        Set.fromList
+          [ metaId meta
+          | meta <- metasAtPoint
+          , not (metaVertical meta)
+          , Set.member (metaId meta) activeAtPoint
+              || Set.member (metaId meta) (eventEnds bundle)
+          ]
+      continuing =
+        Set.fromList
+          [ metaId meta
+          | meta <- metasAtPoint
+          , not (metaVertical meta)
+          , fst (exactPointCoordinates (metaHigh meta)) > x
+          ]
+  pure
+    ( withRelations
+    , removals <> removable
+    , insertions <> continuing
+    , PointChange point continuing : changes
+    )
+
+recordIncidentPairs
+  :: V.Vector SegmentMeta
+  -> EventAccumulation
+  -> Set ExactSweepSegmentId
+  -> Either ExactSegmentEventObstruction EventAccumulation
+recordIncidentPairs metas initial incident =
+  foldM
+    (\accumulation (leftId, rightId) -> do
+       left <- requireMeta metas leftId
+       right <- requireMeta metas rightId
+       recordRelation accumulation left right)
+    initial
+    (unorderedPairs (Set.toAscList incident))
+
+recordRelation
+  :: EventAccumulation
+  -> SegmentMeta
+  -> SegmentMeta
+  -> Either ExactSegmentEventObstruction EventAccumulation
+recordRelation accumulation firstMeta secondMeta =
+  let (leftMeta, rightMeta) =
+        if metaId firstMeta <= metaId secondMeta
+          then (firstMeta, secondMeta)
+          else (secondMeta, firstMeta)
+      leftId = metaId leftMeta
+      rightId = metaId rightMeta
+      relation = relationOf (metaSegment leftMeta) (metaSegment rightMeta)
+      checked = accumulation{accumulatedPairChecks = accumulatedPairChecks accumulation + 1}
+   in case relation of
+        SegmentsDisjoint -> Right checked
+        _ -> do
+          (event, splitPoints) <-
+            relationEvent leftId rightId (metaSegment leftMeta) (metaSegment rightMeta) relation
+          let ExactSweepSegmentId leftIndex = leftId
+              ExactSweepSegmentId rightIndex = rightId
+              splitSet = Set.fromList splitPoints
+              withLeft =
+                IntMap.insertWith Set.union leftIndex splitSet (accumulatedSplits checked)
+              withBoth = IntMap.insertWith Set.union rightIndex splitSet withLeft
+          Right
+            checked
+              { accumulatedEvents =
+                  Map.insert
+                    (leftId, rightId)
+                    event
+                    (accumulatedEvents checked)
+              , accumulatedSplits = withBoth
+              }
+
+relationOf :: ExactSegment -> ExactSegment -> SegmentRelation
+relationOf left right =
+  let (a, b) = exactSegmentEndpoints left
+      (c, d) = exactSegmentEndpoints right
+   in exactSegmentRelation a b c d
+
+relationEvent
+  :: ExactSweepSegmentId
+  -> ExactSweepSegmentId
+  -> ExactSegment
+  -> ExactSegment
+  -> SegmentRelation
+  -> Either ExactSegmentEventObstruction (ExactSegmentEvent, [ExactPoint])
+relationEvent leftId rightId left right relation =
+  case relation of
+    SegmentsDisjoint -> missing
+    SegmentsDuplicate -> Right (ExactDuplicateSegments leftId rightId, [])
+    SegmentsProperlyCross -> do
+      crossing <-
+        either
+          (Left . ExactSweepIntersectionObstruction leftId rightId)
+          Right
+          (exactSupportingLineIntersection left right)
+      Right (ExactProperCrossing leftId rightId crossing, [crossing])
+    SegmentsShareEndpoint ->
+      uniqueWitness (ExactSharedEndpoint leftId rightId) (uniqueShared left right)
+    SegmentEndpointTouchesInterior ->
+      uniqueWitness (ExactEndpointTouch leftId rightId) (uniqueTouch left right)
+    SegmentsCollinearlyOverlap ->
+      let lower = max (min a b) (min c d)
+          upper = min (max a b) (max c d)
+       in if lower < upper
+            then Right (ExactCollinearOverlap leftId rightId lower upper, [lower, upper])
+            else missing
+ where
+  (a, b) = exactSegmentEndpoints left
+  (c, d) = exactSegmentEndpoints right
+  uniqueWitness make witness =
+    case witness of
+      Just point -> Right (make point, [point])
+      Nothing -> missing
+  missing = Left (ExactSweepRelationWitnessMissing leftId rightId relation)
+
+scheduleAroundChange
+  :: V.Vector SegmentMeta
+  -> ExactRational
+  -> StatusTree
+  -> (Map ExactPoint EventBundle, EventAccumulation)
+  -> PointChange
+  -> Either
+      ExactSegmentEventObstruction
+      (Map ExactPoint EventBundle, EventAccumulation)
+scheduleAroundChange metas currentX status state change =
+  case Set.toAscList (changedContinuing change) of
+    [] ->
+      let (_, y) = exactPointCoordinates (changedPoint change)
+          (below, above) = statusBelowAbove currentX y status
+       in scheduleMaybePair metas currentX below above state
+    continuingIds -> do
+      continuing <- traverse (requireMeta metas) continuingIds
+      let ordered = sortBy (statusCompare StatusAfter currentX) continuing
+      case ordered of
+        [] -> Right state
+        lowest : remaining ->
+          let highest = foldl' (\_ current -> current) lowest remaining
+              below = statusPredecessor currentX lowest status
+              above = statusSuccessor currentX highest status
+           in scheduleMaybePair metas currentX below (Just (metaId lowest)) state
+                >>= scheduleMaybePair metas currentX (Just (metaId highest)) above
+
+scheduleMaybePair
+  :: V.Vector SegmentMeta
+  -> ExactRational
+  -> Maybe ExactSweepSegmentId
+  -> Maybe ExactSweepSegmentId
+  -> (Map ExactPoint EventBundle, EventAccumulation)
+  -> Either
+      ExactSegmentEventObstruction
+      (Map ExactPoint EventBundle, EventAccumulation)
+scheduleMaybePair _ _ Nothing _ state = Right state
+scheduleMaybePair _ _ _ Nothing state = Right state
+scheduleMaybePair metas currentX (Just firstId) (Just secondId) (queue, accumulation)
+  | firstId == secondId = Right (queue, accumulation)
+  | otherwise = do
+      firstMeta <- requireMeta metas firstId
+      secondMeta <- requireMeta metas secondId
+      let relation = relationOf (metaSegment firstMeta) (metaSegment secondMeta)
+          checked = accumulation{accumulatedPairChecks = accumulatedPairChecks accumulation + 1}
+      witness <-
+        either
+          (Left . ExactSweepIntersectionObstruction firstId secondId)
+          Right
+          (relationWitnessPoint (metaSegment firstMeta) (metaSegment secondMeta) relation)
+      case witness of
+        Just point
+          | fst (exactPointCoordinates point) > currentX ->
+              let pair = orderedPair firstId secondId
+                  bundle = emptyEventBundle{eventScheduled = Set.singleton pair}
+               in Right (insertBundle point bundle queue, checked)
+        _ -> Right (queue, checked)
+
+relationWitnessPoint
+  :: ExactSegment
+  -> ExactSegment
+  -> SegmentRelation
+  -> Either ExactIntersectionError (Maybe ExactPoint)
+relationWitnessPoint left right relation =
+  case relation of
+    SegmentsProperlyCross -> Just <$> exactSupportingLineIntersection left right
+    SegmentEndpointTouchesInterior -> Right (uniqueTouch left right)
+    SegmentsShareEndpoint -> Right (uniqueShared left right)
+    _ -> Right Nothing
+
+uniqueShared :: ExactSegment -> ExactSegment -> Maybe ExactPoint
+uniqueShared left right =
+  let (a, b) = exactSegmentEndpoints left
+      (c, d) = exactSegmentEndpoints right
+   in case Set.toAscList (Set.intersection (Set.fromList [a, b]) (Set.fromList [c, d])) of
+        [point] -> Just point
+        _ -> Nothing
+
+uniqueTouch :: ExactSegment -> ExactSegment -> Maybe ExactPoint
+uniqueTouch left right =
+  let (a, b) = exactSegmentEndpoints left
+      (c, d) = exactSegmentEndpoints right
+      points =
+        Set.toAscList
+          ( Set.fromList
+              ( [point | point <- [a, b], exactOnClosedSegment c d point]
+                  <> [point | point <- [c, d], exactOnClosedSegment a b point]
+              )
+          )
+   in case points of
+        [point] -> Just point
+        _ -> Nothing
+
+statusCompare
+  :: StatusSide
+  -> ExactRational
+  -> SegmentMeta
+  -> SegmentMeta
+  -> Ordering
+statusCompare side x left right =
+  case compareOrdinateAt x left right of
+    EQ ->
+      case compareSlope left right of
+        EQ -> compare (metaId left) (metaId right)
+        slopeOrder -> if side == StatusAfter then slopeOrder else invertOrdering slopeOrder
+    order -> order
+
+compareOrdinateAt :: ExactRational -> SegmentMeta -> SegmentMeta -> Ordering
+compareOrdinateAt x left right =
+  let (leftNumerator, leftDenominator) = ordinateFraction x left
+      (rightNumerator, rightDenominator) = ordinateFraction x right
+   in compare
+        (leftNumerator * rightDenominator)
+        (rightNumerator * leftDenominator)
+
+compareSlope :: SegmentMeta -> SegmentMeta -> Ordering
+compareSlope left right =
+  let (leftRise, leftRun) = slopeFraction left
+      (rightRise, rightRun) = slopeFraction right
+   in compare
+        (leftRise * rightRun)
+        (rightRise * leftRun)
+
+ordinateFraction :: ExactRational -> SegmentMeta -> (ExactRational, ExactRational)
+ordinateFraction x meta =
+  let (fromX, fromY) = exactPointCoordinates (metaLow meta)
+      (toX, toY) = exactPointCoordinates (metaHigh meta)
+      run = toX - fromX
+      numerator = fromY * run + (x - fromX) * (toY - fromY)
+   in (numerator, run)
+
+slopeFraction :: SegmentMeta -> (ExactRational, ExactRational)
+slopeFraction meta =
+  let (fromX, fromY) = exactPointCoordinates (metaLow meta)
+      (toX, toY) = exactPointCoordinates (metaHigh meta)
+   in (toY - fromY, toX - fromX)
+
+invertOrdering :: Ordering -> Ordering
+invertOrdering LT = GT
+invertOrdering EQ = EQ
+invertOrdering GT = LT
+
+insertStatus
+  :: V.Vector SegmentMeta
+  -> StatusSide
+  -> ExactRational
+  -> StatusTree
+  -> ExactSweepSegmentId
+  -> Either ExactSegmentEventObstruction StatusTree
+insertStatus metas side x tree segmentId = do
+  meta <- requireMeta metas segmentId
+  pure (statusInsert (statusCompare side x) meta tree)
+
+deleteStatus
+  :: V.Vector SegmentMeta
+  -> StatusSide
+  -> ExactRational
+  -> StatusTree
+  -> ExactSweepSegmentId
+  -> Either ExactSegmentEventObstruction StatusTree
+deleteStatus metas side x tree segmentId = do
+  meta <- requireMeta metas segmentId
+  pure (statusDelete (statusCompare side x) meta tree)
+
+statusInsert
+  :: (SegmentMeta -> SegmentMeta -> Ordering)
+  -> SegmentMeta
+  -> StatusTree
+  -> StatusTree
+statusInsert compareIds value tree =
+  case tree of
+    StatusEmpty -> statusNode StatusEmpty value StatusEmpty
+    StatusNode _ left current right ->
+      case compareIds value current of
+        LT -> statusBalance (statusNode (statusInsert compareIds value left) current right)
+        GT -> statusBalance (statusNode left current (statusInsert compareIds value right))
+        EQ -> tree
+
+statusDelete
+  :: (SegmentMeta -> SegmentMeta -> Ordering)
+  -> SegmentMeta
+  -> StatusTree
+  -> StatusTree
+statusDelete compareIds value tree =
+  case tree of
+    StatusEmpty -> StatusEmpty
+    StatusNode _ left current right ->
+      case compareIds value current of
+        LT -> statusBalance (statusNode (statusDelete compareIds value left) current right)
+        GT -> statusBalance (statusNode left current (statusDelete compareIds value right))
+        EQ -> statusMerge left right
+
+statusMerge :: StatusTree -> StatusTree -> StatusTree
+statusMerge left StatusEmpty = left
+statusMerge left right =
+  case statusDeleteLeast right of
+    Nothing -> left
+    Just (least, remaining) -> statusBalance (statusNode left least remaining)
+
+statusDeleteLeast :: StatusTree -> Maybe (SegmentMeta, StatusTree)
+statusDeleteLeast StatusEmpty = Nothing
+statusDeleteLeast (StatusNode _ StatusEmpty value right) = Just (value, right)
+statusDeleteLeast (StatusNode _ left value right) = do
+  (least, remaining) <- statusDeleteLeast left
+  pure (least, statusBalance (statusNode remaining value right))
+
+statusHeight :: StatusTree -> Int
+statusHeight StatusEmpty = 0
+statusHeight (StatusNode height _ _ _) = height
+
+statusNode :: StatusTree -> SegmentMeta -> StatusTree -> StatusTree
+statusNode left value right =
+  StatusNode
+    (1 + max (statusHeight left) (statusHeight right))
+    left
+    value
+    right
+
+statusBalance :: StatusTree -> StatusTree
+statusBalance tree =
+  case tree of
+    StatusEmpty -> StatusEmpty
+    StatusNode _ left value right
+      | statusHeight left - statusHeight right > 1 -> balanceLeft left value right
+      | statusHeight right - statusHeight left > 1 -> balanceRight left value right
+      | otherwise -> statusNode left value right
+
+balanceLeft :: StatusTree -> SegmentMeta -> StatusTree -> StatusTree
+balanceLeft left value right =
+  case left of
+    StatusNode _ leftLeft leftValue leftRight
+      | statusHeight leftLeft >= statusHeight leftRight ->
+          statusNode leftLeft leftValue (statusNode leftRight value right)
+      | otherwise ->
+          case leftRight of
+            StatusNode _ middleLeft middleValue middleRight ->
+              statusNode
+                (statusNode leftLeft leftValue middleLeft)
+                middleValue
+                (statusNode middleRight value right)
+            StatusEmpty -> statusNode left value right
+    StatusEmpty -> statusNode left value right
+
+balanceRight :: StatusTree -> SegmentMeta -> StatusTree -> StatusTree
+balanceRight left value right =
+  case right of
+    StatusNode _ rightLeft rightValue rightRight
+      | statusHeight rightRight >= statusHeight rightLeft ->
+          statusNode (statusNode left value rightLeft) rightValue rightRight
+      | otherwise ->
+          case rightLeft of
+            StatusNode _ middleLeft middleValue middleRight ->
+              statusNode
+                (statusNode left value middleLeft)
+                middleValue
+                (statusNode middleRight rightValue rightRight)
+            StatusEmpty -> statusNode left value right
+    StatusEmpty -> statusNode left value right
+
+statusAtY
+  :: ExactRational
+  -> ExactRational
+  -> StatusTree
+  -> [ExactSweepSegmentId]
+statusAtY x y = descend
+ where
+  descend StatusEmpty = []
+  descend (StatusNode _ left meta right) =
+    case compareMetaToY x y meta of
+      LT -> descend right
+      GT -> descend left
+      EQ -> descend left <> [metaId meta] <> descend right
+
+statusRangeByY
+  :: ExactRational
+  -> ExactRational
+  -> ExactRational
+  -> StatusTree
+  -> [ExactSweepSegmentId]
+statusRangeByY x lower upper = descend
+ where
+  descend StatusEmpty = []
+  descend (StatusNode _ left meta right) =
+    let below = compareMetaToY x lower meta == LT
+        above = compareMetaToY x upper meta == GT
+     in if below
+          then descend right
+          else
+            if above
+              then descend left
+              else descend left <> [metaId meta] <> descend right
+
+compareMetaToY :: ExactRational -> ExactRational -> SegmentMeta -> Ordering
+compareMetaToY x y meta =
+  let (numerator, denominator) = ordinateFraction x meta
+   in compare numerator (y * denominator)
+
+statusBelowAbove
+  :: ExactRational
+  -> ExactRational
+  -> StatusTree
+  -> (Maybe ExactSweepSegmentId, Maybe ExactSweepSegmentId)
+statusBelowAbove x y = descend Nothing Nothing
+ where
+  descend below above StatusEmpty = (below, above)
+  descend below above (StatusNode _ left meta right) =
+    case compareMetaToY x y meta of
+      LT -> descend (Just (metaId meta)) above right
+      GT -> descend below (Just (metaId meta)) left
+      EQ -> (statusGreatest left <|> below, statusLeast right <|> above)
+
+statusPredecessor
+  :: ExactRational
+  -> SegmentMeta
+  -> StatusTree
+  -> Maybe ExactSweepSegmentId
+statusPredecessor x target = descend Nothing
+ where
+  descend candidate StatusEmpty = candidate
+  descend candidate (StatusNode _ left current right) =
+    case statusCompare StatusAfter x target current of
+      LT -> descend candidate left
+      GT -> descend (Just (metaId current)) right
+      EQ -> statusGreatest left <|> candidate
+
+statusSuccessor
+  :: ExactRational
+  -> SegmentMeta
+  -> StatusTree
+  -> Maybe ExactSweepSegmentId
+statusSuccessor x target = descend Nothing
+ where
+  descend candidate StatusEmpty = candidate
+  descend candidate (StatusNode _ left current right) =
+    case statusCompare StatusAfter x target current of
+      LT -> descend (Just (metaId current)) left
+      GT -> descend candidate right
+      EQ -> statusLeast right <|> candidate
+
+statusLeast :: StatusTree -> Maybe ExactSweepSegmentId
+statusLeast StatusEmpty = Nothing
+statusLeast (StatusNode _ StatusEmpty value _) = Just (metaId value)
+statusLeast (StatusNode _ left _ _) = statusLeast left
+
+statusGreatest :: StatusTree -> Maybe ExactSweepSegmentId
+statusGreatest StatusEmpty = Nothing
+statusGreatest (StatusNode _ _ value StatusEmpty) = Just (metaId value)
+statusGreatest (StatusNode _ _ _ right) = statusGreatest right
+
+lookupMeta :: V.Vector SegmentMeta -> ExactSweepSegmentId -> Maybe SegmentMeta
+lookupMeta metas (ExactSweepSegmentId index) = metas V.!? index
+
+requireMeta
+  :: V.Vector SegmentMeta
+  -> ExactSweepSegmentId
+  -> Either ExactSegmentEventObstruction SegmentMeta
+requireMeta metas segmentId =
+  case lookupMeta metas segmentId of
+    Just meta -> Right meta
+    Nothing -> Left (ExactSweepSegmentMissing segmentId)
+
+exactSegmentEvents :: ExactSegmentEventPlan -> [ExactSegmentEvent]
+exactSegmentEvents = Map.elems . plannedEvents
+
+exactSegmentSplitPoints
+  :: ExactSegmentEventPlan
+  -> ExactSweepSegmentId
+  -> [ExactPoint]
+exactSegmentSplitPoints plan (ExactSweepSegmentId segmentIndex) =
+  maybe [] id (plannedSplitPoints plan V.!? segmentIndex)
+
+exactSegmentRelationMap
+  :: ExactSegmentEventPlan
+  -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation
+exactSegmentRelationMap = Map.map eventRelation . plannedEvents
+
+eventRelation :: ExactSegmentEvent -> SegmentRelation
+eventRelation event =
+  case event of
+    ExactProperCrossing {} -> SegmentsProperlyCross
+    ExactEndpointTouch {} -> SegmentEndpointTouchesInterior
+    ExactSharedEndpoint {} -> SegmentsShareEndpoint
+    ExactDuplicateSegments {} -> SegmentsDuplicate
+    ExactCollinearOverlap {} -> SegmentsCollinearlyOverlap
+
+exactSegmentPairChecks :: ExactSegmentEventPlan -> Int
+exactSegmentPairChecks = plannedPairChecks
+
+exactSegmentSweepMaximumHeight :: ExactSegmentEventPlan -> Int
+exactSegmentSweepMaximumHeight = plannedMaximumHeight
+
+sortAlong :: ExactSegment -> [ExactPoint] -> [ExactPoint]
+sortAlong segment =
+  let (from, to) = exactSegmentEndpoints segment
+   in if from <= to then Set.toAscList . Set.fromList else Set.toDescList . Set.fromList
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Mutable.hs b/src-dcel/Moonlight/Triangulation/Internal/Mutable.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/Mutable.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/Mutable.hs
@@ -6,7 +6,18 @@
 
 module Moonlight.Triangulation.Internal.Mutable
   ( MutableDcel (..)
+  , DenseMutableDcel
+  , MutableTopology (..)
+  , denseMutableDcel
+  , denseMutableOwner
+  , DcelCapacity
+  , generalDcelCapacity
+  , planarDcelCapacity
+  , exactDcelCapacity
   , newMutableDcel
+  , DefaultedVertexDcel
+  , newMutableDcelWithVertexDefault
+  , defaultedVertexDcel
   , thawTriangulation
   , thawTriangulationDense
   , freezeTriangulation
@@ -33,6 +44,11 @@
   , edgeOriginPoint
   , appendVertex
   , appendVertexCoordinates
+  , appendDefaultVertexCoordinates
+  , NextVertexSlot
+  , nextVertexSlot
+  , nextVertexSlotIndex
+  , appendVertexCoordinatesAtSlot
   , ensurePointCapacity
   , markConnected
   , isConnected
@@ -40,6 +56,9 @@
   , addEdgeBlock
   , addFace
   , addFaceBlock
+  , denseAddEdgeBlock
+  , denseAddFaceBlock
+  , denseInitializeUnconstrainedEdgeBlock
   , ensureCellCapacity
   , truncatePoints
   , truncateDirectedEdges
@@ -63,6 +82,25 @@
   , readFaceEdge
   , writeFaceEdge
   , readConstraint
+  , denseReadPointX
+  , denseReadPointY
+  , denseReadOrigin
+  , denseWriteOrigin
+  , denseReadNext
+  , denseWriteNext
+  , denseReadPrevious
+  , denseWritePrevious
+  , denseReadFace
+  , denseWriteFace
+  , denseWriteVertexOut
+  , denseMarkFreshConnected
+  , denseCommitFreshConnections
+  , denseReadFaceEdge
+  , denseWriteFaceEdge
+  , denseReadConstraint
+  , denseLinkEdges
+  , denseSetCycle3
+  , denseFaceEdges
   , setConstraint
   , clearConstraint
   ) where
@@ -153,9 +191,193 @@
   , mdElementDefaults :: !(ElementDefaults directed undirected face)
   }
 
-newMutableDcel :: ElementDefaults directed undirected face -> Int -> ST s (MutableDcel s vertex directed undirected face)
-newMutableDcel defaults maximumVertices = newMutableDcelFrom DenseTransaction defaults maximumVertices Nothing
+-- | Physical bounds for one fresh mutable DCEL. Vertex, directed-edge and
+-- face sections are stated independently because a known planar construction
+-- and an arbitrary append program obey different allocation laws. Keeping the
+-- law in the constructor input prevents every fresh caller from silently
+-- inheriting the loosest reservation and then copying its dead tail at freeze.
+data DcelCapacity = DcelCapacity
+  {-# UNPACK #-} !Int
+  {-# UNPACK #-} !Int
+  {-# UNPACK #-} !Int
 
+-- | Conservative append-program capacity, preserving the historical slack
+-- for operations whose intermediate cells may outlive their final topology.
+generalDcelCapacity :: Int -> DcelCapacity
+generalDcelCapacity maximumVertices =
+  let !vertices = max 1 maximumVertices
+   in DcelCapacity vertices (max 2 (8 * vertices + 16)) (max 1 (3 * vertices + 8))
+
+-- | Tight fresh planar capacity. Circle sweep allocates monotonically and no
+-- rewrite abandons a cell, so Euler's bounds plus seed slack are authoritative:
+-- at most @6n@ directed edges and @2n@ faces.
+planarDcelCapacity :: Int -> DcelCapacity
+planarDcelCapacity maximumVertices =
+  let !vertices = max 1 maximumVertices
+   in DcelCapacity vertices (max 2 (6 * vertices + 16)) (max 1 (2 * vertices + 8))
+
+-- | Exact section bounds for reconstruction programs that already know the
+-- published cardinalities they will materialize.
+exactDcelCapacity :: Int -> Int -> Int -> DcelCapacity
+exactDcelCapacity vertices directedEdges faces =
+  DcelCapacity (max 1 vertices) (max 2 directedEdges) (max 1 faces)
+
+-- | The contiguous physical section of one mutable DCEL. It is a refinement
+-- of the canonical owner, not a second mesh: every plane below is the exact
+-- flat vector already held by 'denseMutableOwner'. Circle sweep and dense
+-- sessions discharge this section once, then interpret their hot local
+-- rewrites without re-testing each 'MutablePaged' sum at every cell.
+data DenseMutableDcel s vertex directed undirected face = DenseMutableDcel
+  { dmdOwner :: !(MutableDcel s vertex directed undirected face)
+  , dmdPointX :: !(FlatMutablePaged s Double)
+  , dmdPointY :: !(FlatMutablePaged s Double)
+  , dmdVertexOut :: !(FlatMutablePaged s Word32)
+  , dmdNewConnected :: !(FlatMutablePaged s Word8)
+  , dmdHalfTopology :: !(FlatMutablePaged s Word32)
+  , dmdConstraint :: !(FlatMutablePaged s Word8)
+  , dmdFaceEdge :: !(FlatMutablePaged s Word32)
+  }
+
+denseMutableDcel
+  :: MutableDcel s vertex directed undirected face
+  -> Maybe (DenseMutableDcel s vertex directed undirected face)
+denseMutableDcel owner@MutableDcel{mdPointX, mdPointY, mdVertexOut, mdNewConnected, mdHalfTopology, mdConstraint, mdFaceEdge} =
+  DenseMutableDcel owner
+    <$> flatMutableSection mdPointX
+    <*> flatMutableSection mdPointY
+    <*> flatMutableSection mdVertexOut
+    <*> flatMutableSection mdNewConnected
+    <*> flatMutableSection mdHalfTopology
+    <*> flatMutableSection mdConstraint
+    <*> flatMutableSection mdFaceEdge
+{-# INLINE denseMutableDcel #-}
+
+denseMutableOwner
+  :: DenseMutableDcel s vertex directed undirected face
+  -> MutableDcel s vertex directed undirected face
+denseMutableOwner = dmdOwner
+{-# INLINE denseMutableOwner #-}
+
+-- | The physical interpretation required by local topology rewrites. The
+-- semantic owner remains 'MutableDcel'; this algebra merely preserves the
+-- storage refinement a caller has already proved, so one normalization law
+-- specializes to either paged or flat cells instead of growing a sibling
+-- rewrite engine.
+class MutableTopology mutable where
+  topologyOwner
+    :: mutable s vertex directed undirected face
+    -> MutableDcel s vertex directed undirected face
+  topologyReadPointX
+    :: mutable s vertex directed undirected face -> Int -> ST s Double
+  topologyReadPointY
+    :: mutable s vertex directed undirected face -> Int -> ST s Double
+  topologyReadOrigin
+    :: mutable s vertex directed undirected face -> Int -> ST s Int
+  topologyReadNext
+    :: mutable s vertex directed undirected face -> Int -> ST s Int
+  topologyReadPrevious
+    :: mutable s vertex directed undirected face -> Int -> ST s Int
+  topologyReadFace
+    :: mutable s vertex directed undirected face -> Int -> ST s Int
+  topologyReadConstraint
+    :: mutable s vertex directed undirected face -> Int -> ST s Bool
+  topologyWriteOrigin
+    :: mutable s vertex directed undirected face -> Int -> Int -> ST s ()
+  topologyWriteNext
+    :: mutable s vertex directed undirected face -> Int -> Int -> ST s ()
+  topologyWritePrevious
+    :: mutable s vertex directed undirected face -> Int -> Int -> ST s ()
+  topologyWriteFace
+    :: mutable s vertex directed undirected face -> Int -> Int -> ST s ()
+  topologyWriteFaceEdge
+    :: mutable s vertex directed undirected face -> Int -> Int -> ST s ()
+  topologyWriteVertexOut
+    :: mutable s vertex directed undirected face -> Int -> Int -> ST s ()
+
+instance MutableTopology MutableDcel where
+  topologyOwner = id
+  topologyReadPointX = readPointX
+  topologyReadPointY = readPointY
+  topologyReadOrigin = readOrigin
+  topologyReadNext = readNext
+  topologyReadPrevious = readPrevious
+  topologyReadFace = readFace
+  topologyReadConstraint = readConstraint
+  topologyWriteOrigin = writeOrigin
+  topologyWriteNext = writeNext
+  topologyWritePrevious = writePrevious
+  topologyWriteFace = writeFace
+  topologyWriteFaceEdge = writeFaceEdge
+  topologyWriteVertexOut = writeVertexOut
+  {-# INLINE topologyOwner #-}
+  {-# INLINE topologyReadPointX #-}
+  {-# INLINE topologyReadPointY #-}
+  {-# INLINE topologyReadOrigin #-}
+  {-# INLINE topologyReadNext #-}
+  {-# INLINE topologyReadPrevious #-}
+  {-# INLINE topologyReadFace #-}
+  {-# INLINE topologyReadConstraint #-}
+  {-# INLINE topologyWriteOrigin #-}
+  {-# INLINE topologyWriteNext #-}
+  {-# INLINE topologyWritePrevious #-}
+  {-# INLINE topologyWriteFace #-}
+  {-# INLINE topologyWriteFaceEdge #-}
+  {-# INLINE topologyWriteVertexOut #-}
+
+instance MutableTopology DenseMutableDcel where
+  topologyOwner = denseMutableOwner
+  topologyReadPointX = denseReadPointX
+  topologyReadPointY = denseReadPointY
+  topologyReadOrigin = denseReadOrigin
+  topologyReadNext = denseReadNext
+  topologyReadPrevious = denseReadPrevious
+  topologyReadFace = denseReadFace
+  topologyReadConstraint = denseReadConstraint
+  topologyWriteOrigin = denseWriteOrigin
+  topologyWriteNext = denseWriteNext
+  topologyWritePrevious = denseWritePrevious
+  topologyWriteFace = denseWriteFace
+  topologyWriteFaceEdge = denseWriteFaceEdge
+  topologyWriteVertexOut = denseWriteVertexOut
+  {-# INLINE topologyOwner #-}
+  {-# INLINE topologyReadPointX #-}
+  {-# INLINE topologyReadPointY #-}
+  {-# INLINE topologyReadOrigin #-}
+  {-# INLINE topologyReadNext #-}
+  {-# INLINE topologyReadPrevious #-}
+  {-# INLINE topologyReadFace #-}
+  {-# INLINE topologyReadConstraint #-}
+  {-# INLINE topologyWriteOrigin #-}
+  {-# INLINE topologyWriteNext #-}
+  {-# INLINE topologyWritePrevious #-}
+  {-# INLINE topologyWriteFace #-}
+  {-# INLINE topologyWriteFaceEdge #-}
+  {-# INLINE topologyWriteVertexOut #-}
+
+newMutableDcel :: ElementDefaults directed undirected face -> DcelCapacity -> ST s (MutableDcel s vertex directed undirected face)
+newMutableDcel defaults capacity = newMutableDcelFrom DenseTransaction Nothing defaults capacity Nothing
+
+-- | A fresh mutable DCEL whose vertex payload plane is uniformly filled.
+-- The witness is what permits geometry-only ingress to extend that plane
+-- without materializing one boxed unit value per site.
+newtype DefaultedVertexDcel s vertex directed undirected face = DefaultedVertexDcel
+  { defaultedVertexDcel :: MutableDcel s vertex directed undirected face
+  }
+
+newMutableDcelWithVertexDefault
+  :: vertex
+  -> ElementDefaults directed undirected face
+  -> DcelCapacity
+  -> ST s (DefaultedVertexDcel s vertex directed undirected face)
+newMutableDcelWithVertexDefault vertexDefault defaults capacity =
+  DefaultedVertexDcel
+    <$> newMutableDcelFrom
+          DenseTransaction
+          (Just vertexDefault)
+          defaults
+          capacity
+          Nothing
+
 -- | Open a local-edit transaction: copy-on-write pages, publication
 -- proportional to dirtied pages. The section for singleton persistent verbs.
 thawTriangulation
@@ -165,8 +387,9 @@
 thawTriangulation maximumVertices triangulation =
   newMutableDcelFrom
     LocalTransaction
+    Nothing
     (triElementDefaults triangulation)
-    maximumVertices
+    (generalDcelCapacity maximumVertices)
     (Just triangulation)
 
 -- | Open a batch transaction: one dense copy up front, flat reads and writes
@@ -178,32 +401,26 @@
 thawTriangulationDense maximumVertices triangulation =
   newMutableDcelFrom
     DenseTransaction
+    Nothing
     (triElementDefaults triangulation)
-    maximumVertices
+    (generalDcelCapacity maximumVertices)
     (Just triangulation)
 
 newMutableDcelFrom
   :: TransactionShape
+  -> Maybe vertex
   -> ElementDefaults directed undirected face
-  -> Int
+  -> DcelCapacity
   -> Maybe (Triangulation mode vertex directed undirected face)
   -> ST s (MutableDcel s vertex directed undirected face)
-newMutableDcelFrom shape mdElementDefaults maximumVertices source = do
+newMutableDcelFrom shape vertexDefault mdElementDefaults (DcelCapacity requestedVertices requestedHalfEdges requestedFaces) source = do
   let !existingVertices = maybe 0 (pagedLength . triPointX) source
       !existingHalfEdges = maybe 0 ((`quot` 4) . pagedLength . triHalfTopology) source
       !existingFaces = maybe 1 (pagedLength . triFaceEdge) source
-      !growthVertexCapacity = max 1 (max existingVertices maximumVertices)
-      !vertexCapacity = growthVertexCapacity
-      -- Euler bounds a planar triangulation's LIVE cells at 3n - 6 undirected
-      -- edges and 2n - 4 faces, but these arenas are append-only with no free
-      -- list, so what must be reserved is CUMULATIVE allocation, not the live
-      -- maximum. Measured: a complete 5-vertex triangulation (9 edges, 6 faces,
-      -- V - E + F = 2) requests a seventh face without gaining a vertex, so
-      -- some operation abandons a slot it will never reclaim. The slack below
-      -- is headroom over that leakage, not a bound derived from it.
-      !halfCapacity = max existingHalfEdges (max 2 (8 * growthVertexCapacity + 16))
-      !faceCapacity = max existingFaces (max 1 (3 * growthVertexCapacity + 8))
-      vertexDataBase = maybe (emptyBoxedPaged Nothing) triVertexData source
+      !vertexCapacity = max existingVertices requestedVertices
+      !halfCapacity = max existingHalfEdges requestedHalfEdges
+      !faceCapacity = max existingFaces requestedFaces
+      vertexDataBase = maybe (emptyBoxedPaged vertexDefault) triVertexData source
       directedDataBase = maybe (emptyBoxedPaged (Just (defaultDirectedEdgeData mdElementDefaults))) triDirectedData source
       undirectedDataBase = maybe (emptyBoxedPaged (Just (defaultUndirectedEdgeData mdElementDefaults))) triUndirectedData source
       faceDataBase = maybe (emptyBoxedPaged (Just (defaultFaceData mdElementDefaults))) triFaceData source
@@ -527,6 +744,22 @@
     Point x y -> appendVertexCoordinates mutable x y vertexData
 {-# INLINE appendVertex #-}
 
+-- | The next unmaterialized vertex record in one mutable DCEL. The constructor
+-- stays private: a caller may transport the candidate to a derived identity
+-- index, but cannot fabricate a different arena position for the append that
+-- follows. The slot remains lawful only while no intervening append occurs.
+newtype NextVertexSlot s = NextVertexSlot Int
+
+nextVertexSlot
+  :: MutableDcel s vertex directed undirected face
+  -> ST s (NextVertexSlot s)
+nextVertexSlot MutableDcel{mdPointCount} = NextVertexSlot <$> readSTRef mdPointCount
+{-# INLINE nextVertexSlot #-}
+
+nextVertexSlotIndex :: NextVertexSlot s -> Int
+nextVertexSlotIndex (NextVertexSlot vertex) = vertex
+{-# INLINE nextVertexSlotIndex #-}
+
 -- | Append a vertex whose coordinates are already canonical, by components.
 -- This is the one owner of the append record; 'appendVertex' is its
 -- t'Point'-carrying form for callers holding a point. Raw capacity means the
@@ -537,35 +770,73 @@
   -> Double
   -> vertex
   -> ST s Int
-appendVertexCoordinates mutable@MutableDcel{mdPointCount, mdPointX, mdPointY, mdPointIndex, mdNewConnected, mdRecycledNew} x y vertexData = do
-  vertex <- readSTRef mdPointCount
+appendVertexCoordinates mutable x y vertexData = do
+  slot <- nextVertexSlot mutable
+  appendVertexCoordinatesAtSlot mutable slot x y vertexData
+{-# INLINE appendVertexCoordinates #-}
+
+-- | Append the uniform vertex payload carried by a defaulted-vertex witness.
+-- No boxed page is written: extending the authoritative point count extends
+-- the defaulted payload plane at freeze.
+appendDefaultVertexCoordinates
+  :: DefaultedVertexDcel s vertex directed undirected face
+  -> Double
+  -> Double
+  -> ST s Int
+appendDefaultVertexCoordinates (DefaultedVertexDcel mutable) x y = do
+  slot <- nextVertexSlot mutable
+  initializeVertexCoordinatesAtSlot mutable slot x y
+{-# INLINE appendDefaultVertexCoordinates #-}
+
+-- | Materialize the exact fresh record named by a previously acquired slot.
+-- Keeping the slot typed and adjacent to identity resolution lets bulk ingress
+-- share one point-count read between the identity candidate and the append.
+appendVertexCoordinatesAtSlot
+  :: MutableDcel s vertex directed undirected face
+  -> NextVertexSlot s
+  -> Double
+  -> Double
+  -> vertex
+  -> ST s Int
+appendVertexCoordinatesAtSlot mutable slot@(NextVertexSlot vertex) x y vertexData = do
+  writeVertexData mutable vertex vertexData
+  initializeVertexCoordinatesAtSlot mutable slot x y
+{-# INLINE appendVertexCoordinatesAtSlot #-}
+
+initializeVertexCoordinatesAtSlot
+  :: MutableDcel s vertex directed undirected face
+  -> NextVertexSlot s
+  -> Double
+  -> Double
+  -> ST s Int
+initializeVertexCoordinatesAtSlot mutable@MutableDcel{mdPointCount, mdPointX, mdPointY, mdPointIndex, mdNewConnected, mdRecycledNew} (NextVertexSlot vertex) x y = do
   writePaged mdPointX vertex x
   writePaged mdPointY vertex y
-  writeVertexData mutable vertex vertexData
   writeVertexOut mutable vertex (-1)
   if vertex >= mdInitialPointCount mutable
     then writePaged mdNewConnected (vertex - mdInitialPointCount mutable) 0
     else modifySTRef' mdRecycledNew (IntSet.insert vertex)
-  modifySTRef'
-    mdPointIndex
-    (\pointIndexState ->
-       case pointIndexState of
-         ActivePersistentPointIndex pointIndex ->
-           ActivePersistentPointIndex (insertPointIndex x y vertex pointIndex)
-         -- The batch table is only lawful over the removal subprogram that
-         -- opened it. An insertion before that scope is closed invalidates the
-         -- derived cache rather than pretending an unregistered handle exists.
-         ActiveBatchPointIndex _ -> MissingPointIndex
-         -- Transported lazily: the field holds a pure update thunk, so a batch
-         -- that never asks an identity question pays one allocation per append,
-         -- while a persistent chain that asks every publication forces a
-         -- depth-one thunk instead of rebuilding the index over the whole mesh.
-         DormantPointIndex pointIndex ->
-           DormantPointIndex (insertPointIndex x y vertex pointIndex)
-         MissingPointIndex -> MissingPointIndex
-    )
+  pointIndexState <- readSTRef mdPointIndex
+  case pointIndexState of
+    ActivePersistentPointIndex pointIndex ->
+      writeSTRef mdPointIndex (ActivePersistentPointIndex (insertPointIndex x y vertex pointIndex))
+    -- The batch table is only lawful over the removal subprogram that opened
+    -- it. An insertion before that scope is closed invalidates the derived
+    -- cache rather than pretending an unregistered handle exists.
+    ActiveBatchPointIndex _ -> writeSTRef mdPointIndex MissingPointIndex
+    -- Transported lazily: the field holds a pure update thunk, so a batch that
+    -- never asks an identity question pays one allocation per append, while a
+    -- persistent chain that asks every publication forces a depth-one thunk
+    -- instead of rebuilding the index over the whole mesh.
+    DormantPointIndex pointIndex ->
+      writeSTRef mdPointIndex (DormantPointIndex (insertPointIndex x y vertex pointIndex))
+    -- A missing derived view stays missing without writing its cell once per
+    -- bulk vertex. Freeze already descends from the coordinate authority when
+    -- a future identity query demands the view.
+    MissingPointIndex -> pure ()
   writeSTRef mdPointCount (vertex + 1)
   pure vertex
+{-# INLINE initializeVertexCoordinatesAtSlot #-}
 
 -- | Check the point arena before a local rewrite materializes vertices. The
 -- caller performs this before the first write, so refusal needs no rollback.
@@ -630,6 +901,33 @@
   pure base
 {-# INLINE addEdgeBlock #-}
 
+-- | Append initialized cells through the already-proved contiguous section.
+-- The semantic allocator and its tail invariant are identical to
+-- 'addEdgeBlock'; only the physical interpreter is selected once rather than
+-- once per topology slot.
+denseAddEdgeBlock :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseAddEdgeBlock dense@DenseMutableDcel{dmdOwner = MutableDcel{mdHalfCount}} pairs = do
+  base <- readSTRef mdHalfCount
+  let !required = base + 2 * pairs
+  denseInitializeUnconstrainedEdgeBlock dense base pairs
+  writeSTRef mdHalfCount required
+  pure base
+{-# INLINE denseAddEdgeBlock #-}
+
+-- | Initialize the constraint section of a proved fresh directed-edge block
+-- without advancing the global arena count. A reserved bulk program threads
+-- its allocation cursor immutably and commits the count once after gluing;
+-- the ordinary allocator above shares this exact record initializer.
+denseInitializeUnconstrainedEdgeBlock
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Int
+  -> Int
+  -> ST s ()
+denseInitializeUnconstrainedEdgeBlock DenseMutableDcel{dmdConstraint} directedBase pairs =
+  forM_ [directedBase `quot` 2 .. directedBase `quot` 2 + pairs - 1] $ \edge ->
+    writeFlatMutable dmdConstraint edge 0
+{-# INLINE denseInitializeUnconstrainedEdgeBlock #-}
+
 addFace :: MutableDcel s vertex directed undirected face -> Int -> ST s Int
 addFace mutable anchor = do
   base <- addFaceBlock mutable 1
@@ -646,6 +944,14 @@
   pure base
 {-# INLINE addFaceBlock #-}
 
+denseAddFaceBlock :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseAddFaceBlock DenseMutableDcel{dmdOwner = MutableDcel{mdFaceCount}} count = do
+  base <- readSTRef mdFaceCount
+  let !required = base + count
+  writeSTRef mdFaceCount required
+  pure base
+{-# INLINE denseAddFaceBlock #-}
+
 -- | Check the local allocation section before any topology rewrite begins.
 -- A refusal leaves every mutable plane untouched, so the enclosing transaction
 -- can abandon publication without rollback machinery.
@@ -980,6 +1286,110 @@
   e2 <- readNext mutable e1
   pure (e0, e1, e2)
 {-# INLINE faceEdges #-}
+
+denseLinkEdges :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseLinkEdges dense left right = do
+  denseWriteNext dense left right
+  denseWritePrevious dense right left
+{-# INLINE denseLinkEdges #-}
+
+denseSetCycle3 :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> Int -> Int -> ST s ()
+denseSetCycle3 dense face e0 e1 e2 = do
+  denseWriteNext dense e0 e1
+  denseWriteNext dense e1 e2
+  denseWriteNext dense e2 e0
+  denseWritePrevious dense e0 e2
+  denseWritePrevious dense e1 e0
+  denseWritePrevious dense e2 e1
+  denseWriteFace dense e0 face
+  denseWriteFace dense e1 face
+  denseWriteFace dense e2 face
+  denseWriteFaceEdge dense face e0
+{-# INLINE denseSetCycle3 #-}
+
+denseFaceEdges :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s (Int, Int, Int)
+denseFaceEdges dense face = do
+  e0 <- denseReadFaceEdge dense face
+  e1 <- denseReadNext dense e0
+  e2 <- denseReadNext dense e1
+  pure (e0, e1, e2)
+{-# INLINE denseFaceEdges #-}
+
+denseReadPointX :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Double
+denseReadPointX DenseMutableDcel{dmdPointX} = readFlatMutable dmdPointX
+denseReadPointY :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Double
+denseReadPointY DenseMutableDcel{dmdPointY} = readFlatMutable dmdPointY
+{-# INLINE denseReadPointX #-}
+{-# INLINE denseReadPointY #-}
+
+denseReadOrigin :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseReadOrigin DenseMutableDcel{dmdHalfTopology} index = fromIntegral <$> readFlatMutable dmdHalfTopology (4 * index)
+denseReadNext :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseReadNext DenseMutableDcel{dmdHalfTopology} index = fromIntegral <$> readFlatMutable dmdHalfTopology (4 * index + 1)
+denseReadPrevious :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseReadPrevious DenseMutableDcel{dmdHalfTopology} index = fromIntegral <$> readFlatMutable dmdHalfTopology (4 * index + 2)
+denseReadFace :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseReadFace DenseMutableDcel{dmdHalfTopology} index = fromIntegral <$> readFlatMutable dmdHalfTopology (4 * index + 3)
+{-# INLINE denseReadOrigin #-}
+{-# INLINE denseReadNext #-}
+{-# INLINE denseReadPrevious #-}
+{-# INLINE denseReadFace #-}
+
+denseWriteOrigin :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseWriteOrigin DenseMutableDcel{dmdHalfTopology} index value = writeFlatMutable dmdHalfTopology (4 * index) (packIndex value)
+denseWriteNext :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseWriteNext DenseMutableDcel{dmdHalfTopology} index value = writeFlatMutable dmdHalfTopology (4 * index + 1) (packIndex value)
+denseWritePrevious :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseWritePrevious DenseMutableDcel{dmdHalfTopology} index value = writeFlatMutable dmdHalfTopology (4 * index + 2) (packIndex value)
+denseWriteFace :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseWriteFace DenseMutableDcel{dmdHalfTopology} index value = writeFlatMutable dmdHalfTopology (4 * index + 3) (packIndex value)
+{-# INLINE denseWriteOrigin #-}
+{-# INLINE denseWriteNext #-}
+{-# INLINE denseWritePrevious #-}
+{-# INLINE denseWriteFace #-}
+
+denseWriteVertexOut :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseWriteVertexOut DenseMutableDcel{dmdVertexOut} index value =
+  writeFlatMutable dmdVertexOut index (if value < 0 then noIndex else packIndex value)
+{-# INLINE denseWriteVertexOut #-}
+
+-- | Materialize the connectivity section for a vertex proven fresh by the
+-- circle-sweep reservation. Arbitrary insertion retains 'markConnected'; this
+-- refined write has no resident case to rediscover, and its aggregate count is
+-- committed once after the sweep's local sections glue.
+denseMarkFreshConnected
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Int
+  -> Int
+  -> ST s ()
+denseMarkFreshConnected dense@DenseMutableDcel{dmdOwner = MutableDcel{mdInitialPointCount}, dmdNewConnected} vertex outgoing = do
+  writeFlatMutable dmdNewConnected (vertex - mdInitialPointCount) 1
+  denseWriteVertexOut dense vertex outgoing
+{-# INLINE denseMarkFreshConnected #-}
+
+denseCommitFreshConnections
+  :: DenseMutableDcel s vertex directed undirected face
+  -> Int
+  -> ST s ()
+denseCommitFreshConnections DenseMutableDcel{dmdOwner = MutableDcel{mdConnectedCount}} count =
+  modifySTRef' mdConnectedCount (+ count)
+{-# INLINE denseCommitFreshConnections #-}
+
+denseReadFaceEdge :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Int
+denseReadFaceEdge DenseMutableDcel{dmdFaceEdge} index = do
+  value <- readFlatMutable dmdFaceEdge index
+  pure (if value == noIndex then -1 else fromIntegral value)
+{-# INLINE denseReadFaceEdge #-}
+
+denseWriteFaceEdge :: DenseMutableDcel s vertex directed undirected face -> Int -> Int -> ST s ()
+denseWriteFaceEdge DenseMutableDcel{dmdFaceEdge} index value =
+  writeFlatMutable dmdFaceEdge index (if value < 0 then noIndex else packIndex value)
+{-# INLINE denseWriteFaceEdge #-}
+
+denseReadConstraint :: DenseMutableDcel s vertex directed undirected face -> Int -> ST s Bool
+denseReadConstraint DenseMutableDcel{dmdConstraint} directed =
+  (/= 0) <$> readFlatMutable dmdConstraint (directed `quot` 2)
+{-# INLINE denseReadConstraint #-}
 
 readOrigin :: MutableDcel s vertex directed undirected face -> Int -> ST s Int
 readOrigin MutableDcel{mdHalfTopology} index = fromIntegral <$> readPaged mdHalfTopology (4 * index)
diff --git a/src-dcel/Moonlight/Triangulation/Internal/OperationState.hs b/src-dcel/Moonlight/Triangulation/Internal/OperationState.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/OperationState.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/OperationState.hs
@@ -11,6 +11,7 @@
 -- probe) charge them where they happen.
 module Moonlight.Triangulation.Internal.OperationState
   ( Counter (..)
+  , LegalizationArena (..)
   , OperationState
   , newOperationState
   , legalizationArena
@@ -38,6 +39,12 @@
 import Moonlight.Triangulation.Internal.PackedIndex (packIndex)
 import Moonlight.Triangulation.Internal.Types (BuildStats (..))
 
+-- | The typed candidate section owned by one operation. The newtype prevents
+-- unrelated scratch vectors from being handed to the normalizer while
+-- erasing to the same contiguous Word32 arena in the hot path.
+newtype LegalizationArena s = LegalizationArena
+  (MUV.MVector s Word32)
+
 -- | Instrumentation cells. The first twenty constructors are exactly the
 -- t'BuildStats' fields, in t'BuildStats' order; anything after
 -- 'CounterRefinementQueuePops' is a diagnostic with no t'BuildStats' field and
@@ -79,7 +86,7 @@
 -- carries the vector and all stack metrics strictly, and stores it once when
 -- finished; there is no per-candidate reference traffic.
 data OperationState s = OperationState
-  { osLegalizationArena :: !(STRef s (MUV.MVector s Word32))
+  { osLegalizationArena :: !(STRef s (LegalizationArena s))
   , osScratchArena :: !(GrowableWord32 s)
   , osCounters :: !(MUV.MVector s Word64)
   }
@@ -100,7 +107,7 @@
 -- magnitude larger than the edit.
 newOperationState :: Int -> ST s (OperationState s)
 newOperationState halfEdgeCapacity = do
-  initialArena <- MUV.new (min (2 * halfEdgeCapacity + 64) initialLegalizationReservation)
+  initialArena <- LegalizationArena <$> MUV.new (min (2 * halfEdgeCapacity + 64) initialLegalizationReservation)
   arena <- newSTRef initialArena
   scratch <- newGrowableWord32 (min 64 (halfEdgeCapacity + 8))
   counters <- MUV.replicate (fromEnum CounterCount) 0
@@ -117,11 +124,11 @@
 initialLegalizationReservation :: Int
 initialLegalizationReservation = 64
 
-legalizationArena :: OperationState s -> ST s (MUV.MVector s Word32)
+legalizationArena :: OperationState s -> ST s (LegalizationArena s)
 legalizationArena = readSTRef . osLegalizationArena
 {-# INLINE legalizationArena #-}
 
-storeLegalizationArena :: OperationState s -> MUV.MVector s Word32 -> ST s ()
+storeLegalizationArena :: OperationState s -> LegalizationArena s -> ST s ()
 storeLegalizationArena = writeSTRef . osLegalizationArena
 {-# INLINE storeLegalizationArena #-}
 
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Region/Bounds.hs b/src-dcel/Moonlight/Triangulation/Internal/Region/Bounds.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/Region/Bounds.hs
@@ -0,0 +1,205 @@
+-- | Exact axis-aligned candidate bounds for admitted region geometry. Bounds
+-- prune impossible overlap obligations; exact predicates remain authoritative.
+module Moonlight.Triangulation.Internal.Region.Bounds
+  ( ExactBounds
+  , exactLoopBounds
+  , componentBounds
+  , regionBounds
+  , boundsOverlap
+  , pointInBounds
+  , overlappingPairs
+  , overlappingOptionalPairs
+  , overlappingPairsBetween
+  , overlappingPredecessors
+  ) where
+
+import Data.List (sortOn, tails)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.IntMap.Strict as IntMap
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , exactPointCoordinates
+  )
+import Moonlight.Triangulation.Internal.ExactRational (ExactRational)
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop (..)
+  , PlanarRegion (..)
+  , PolygonComponent (..)
+  )
+
+data ExactBounds = ExactBounds
+  !ExactRational
+  !ExactRational
+  !ExactRational
+  !ExactRational
+
+exactLoopBounds :: ExactLoop -> ExactBounds
+exactLoopBounds (ExactLoop (firstPoint :| remaining)) =
+  foldl' extend (pointBounds firstPoint) remaining
+ where
+  extend bounds point = boundsUnion bounds (pointBounds point)
+
+componentBounds :: PolygonComponent -> ExactBounds
+componentBounds = exactLoopBounds . polygonOuterLoop
+
+regionBounds :: PlanarRegion -> Maybe ExactBounds
+regionBounds (PlanarRegion components) =
+  case components of
+    [] -> Nothing
+    firstComponent : remaining ->
+      Just
+        ( foldl'
+            (\bounds component -> boundsUnion bounds (componentBounds component))
+            (componentBounds firstComponent)
+            remaining
+        )
+
+pointBounds :: ExactPoint -> ExactBounds
+pointBounds point =
+  let (x, y) = exactPointCoordinates point
+   in ExactBounds x y x y
+
+boundsUnion :: ExactBounds -> ExactBounds -> ExactBounds
+boundsUnion
+  (ExactBounds leftMinimumX leftMinimumY leftMaximumX leftMaximumY)
+  (ExactBounds rightMinimumX rightMinimumY rightMaximumX rightMaximumY) =
+    ExactBounds
+      (min leftMinimumX rightMinimumX)
+      (min leftMinimumY rightMinimumY)
+      (max leftMaximumX rightMaximumX)
+      (max leftMaximumY rightMaximumY)
+
+boundsOverlap :: ExactBounds -> ExactBounds -> Bool
+boundsOverlap
+  (ExactBounds leftMinimumX leftMinimumY leftMaximumX leftMaximumY)
+  (ExactBounds rightMinimumX rightMinimumY rightMaximumX rightMaximumY) =
+    not
+      ( leftMaximumX < rightMinimumX
+          || rightMaximumX < leftMinimumX
+          || leftMaximumY < rightMinimumY
+          || rightMaximumY < leftMinimumY
+      )
+
+pointInBounds :: ExactPoint -> ExactBounds -> Bool
+pointInBounds point (ExactBounds minimumX minimumY maximumX maximumY) =
+  let (x, y) = exactPointCoordinates point
+   in minimumX <= x && x <= maximumX && minimumY <= y && y <= maximumY
+
+overlappingPairs :: (value -> ExactBounds) -> [value] -> [(value, value)]
+overlappingPairs boundsOf =
+  overlappingIndexedValuePairs
+    . zipWith (\index value -> (index, boundsOf value, value)) [0 ..]
+
+overlappingOptionalPairs
+  :: (value -> Maybe ExactBounds)
+  -> [value]
+  -> [(value, value)]
+overlappingOptionalPairs boundsOf =
+  overlappingIndexedValuePairs
+    . concatMap
+      (\(index, value) ->
+         case boundsOf value of
+           Nothing -> []
+           Just bounds -> [(index, bounds, value)])
+    . zip [0 ..]
+
+overlappingPairsBetween
+  :: (value -> ExactBounds)
+  -> [value]
+  -> [value]
+  -> [(value, value)]
+overlappingPairsBetween boundsOf leftValues rightValues =
+  [ pair
+  | (firstBounds, firstValue) : remaining <- tails ordered
+  , (secondBounds, secondValue) <-
+      takeWhile
+        (\(bounds, _) -> boundsMinimumX bounds <= boundsMaximumX firstBounds)
+        remaining
+  , pair <- crossPair firstValue secondValue
+  , boundsOverlap firstBounds secondBounds
+  ]
+ where
+  ordered =
+    sortOn
+      (boundsMinimumX . fst)
+      ( map
+          (\value -> (either boundsOf boundsOf value, value))
+          (map Left leftValues <> map Right rightValues)
+      )
+  crossPair :: Either value value -> Either value value -> [(value, value)]
+  crossPair (Left leftValue) (Right rightValue) = [(leftValue, rightValue)]
+  crossPair (Right rightValue) (Left leftValue) = [(leftValue, rightValue)]
+  crossPair _ _ = []
+
+-- | Each value together with only the earlier input values whose exact bounds
+-- overlap it. This is the local cover used when a fold glues one component at
+-- a time and needs the complete overlap section accumulated so far.
+overlappingPredecessors
+  :: (value -> ExactBounds)
+  -> [value]
+  -> [(value, [value])]
+overlappingPredecessors boundsOf values =
+  [ ( value
+    , map snd
+        ( sortOn fst
+            (IntMap.findWithDefault [] index predecessorsByIndex)
+        )
+    )
+  | (index, value) <- zip [0 ..] values
+  ]
+ where
+  indexed = zipWith (\index value -> (index, boundsOf value, value)) [0 ..] values
+  predecessorsByIndex =
+    IntMap.fromListWith (<>)
+      [ (rightIndex, [(leftIndex, leftValue)])
+      | (leftIndex, rightIndex, leftValue, _) <- overlappingIndexedEntries indexed
+      ]
+
+-- The authoring path retains its nested result pair because the flattened
+-- four-field candidate increased allocation on the registered 1,024-component
+-- workload. The predecessor path below needs the indices after selection and
+-- therefore carries the indexed specialization separately.
+overlappingIndexedValuePairs
+  :: [(Int, ExactBounds, value)]
+  -> [(value, value)]
+overlappingIndexedValuePairs indexed =
+  map (\(_, _, pair) -> pair)
+    ( sortOn (\(leftIndex, rightIndex, _) -> (leftIndex, rightIndex))
+        [ if leftIndex <= rightIndex
+            then (leftIndex, rightIndex, (leftValue, rightValue))
+            else (rightIndex, leftIndex, (rightValue, leftValue))
+        | (leftIndex, leftBounds, leftValue) : remaining <- tails ordered
+        , (rightIndex, rightBounds, rightValue) <-
+            takeWhile
+              (\(_, bounds, _) -> boundsMinimumX bounds <= boundsMaximumX leftBounds)
+              remaining
+        , boundsOverlap leftBounds rightBounds
+        ]
+    )
+ where
+  ordered = sortOn (\(_, bounds, _) -> boundsMinimumX bounds) indexed
+
+overlappingIndexedEntries
+  :: [(Int, ExactBounds, value)]
+  -> [(Int, Int, value, value)]
+overlappingIndexedEntries indexed =
+  sortOn
+    (\(leftIndex, rightIndex, _, _) -> (leftIndex, rightIndex))
+    [ if leftIndex <= rightIndex
+        then (leftIndex, rightIndex, leftValue, rightValue)
+        else (rightIndex, leftIndex, rightValue, leftValue)
+    | (leftIndex, leftBounds, leftValue) : remaining <- tails ordered
+    , (rightIndex, rightBounds, rightValue) <-
+        takeWhile
+          (\(_, bounds, _) -> boundsMinimumX bounds <= boundsMaximumX leftBounds)
+          remaining
+    , boundsOverlap leftBounds rightBounds
+    ]
+ where
+  ordered = sortOn (\(_, bounds, _) -> boundsMinimumX bounds) indexed
+
+boundsMinimumX :: ExactBounds -> ExactRational
+boundsMinimumX (ExactBounds minimumX _ _ _) = minimumX
+
+boundsMaximumX :: ExactBounds -> ExactRational
+boundsMaximumX (ExactBounds _ _ maximumX _) = maximumX
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Region/Publication.hs b/src-dcel/Moonlight/Triangulation/Internal/Region/Publication.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/Region/Publication.hs
@@ -0,0 +1,153 @@
+-- | Trusted exact-coordinate publication from the one resident DCEL boundary
+-- owner. The callback-bearing entrances are internal because only a sealed
+-- downstream carrier may prove that its exact coordinate section belongs to
+-- the supplied topology.
+module Moonlight.Triangulation.Internal.Region.Publication
+  ( labelledPlanarLayer
+  , labelledPlanarLayerFromExactCoordinates
+  , planarLayerFromAdmittedComponents
+  , polygonComponentFromBoundaryCoordinates
+  ) where
+
+import Data.Bifunctor (first)
+import Data.List (sort)
+import Data.List.NonEmpty (NonEmpty)
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Map.Strict as Map
+import Moonlight.Triangulation.Dcel (vertexPoint)
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , exactOnClosedSegment
+  , exactOrient2d
+  , exactPointFromPoint
+  )
+import Moonlight.Triangulation.FloodFillIterator
+  ( BoundaryLoop
+  , RegionBoundary
+  , boundaryLoopVertices
+  , componentBoundary
+  , faceComponents
+  , labelledRegionBoundaries
+  , regionBoundaryHoleLoops
+  , regionBoundaryOuterLoop
+  )
+import Moonlight.Triangulation.Handles.HandleDefs (FaceId, VertexId)
+import Moonlight.Triangulation.Internal.BoundaryCycle
+  ( rotateCycleLeast
+  , simplifyBoundaryCycle
+  )
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop (..)
+  , PlanarLayer (..)
+  , PlanarRegion (..)
+  , PolygonComponent (..)
+  , RegionPublicationError (..)
+  , RegionValidationError (..)
+  )
+import Moonlight.Triangulation.Internal.Representation (Triangulation)
+
+-- | Publish bounded resident face labels through the existing component and
+-- boundary owners, then lift only traced boundary coordinates exactly.
+labelledPlanarLayer
+  :: Ord label
+  => label
+  -> Triangulation mode vertex directed undirected face
+  -> (FaceId -> label)
+  -> Either RegionPublicationError (PlanarLayer label)
+labelledPlanarLayer outside triangulation labelFace = do
+  labelledBoundaries <-
+    first RegionBoundaryObstruction
+      (labelledRegionBoundaries triangulation labelFace)
+  labelledComponents <-
+    traverse
+      (\(label, boundary) ->
+         (label,) <$> polygonComponentFromBoundaryCoordinates exactPointAt boundary)
+      [ pair
+      | pair@(label, _) <- labelledBoundaries
+      , label /= outside
+      ]
+  pure (planarLayerFromAdmittedComponents outside labelledComponents)
+ where
+  exactPointAt vertex =
+    first (RegionCoordinateObstruction vertex)
+      (exactPointFromPoint (vertexPoint triangulation vertex))
+
+-- | Publish bounded face labels using the exact-coordinate section belonging
+-- to the resident carrier. Topology has already been proved by
+-- 'labelledRegionBoundaries'; this path performs exact simplification but does
+-- not send derived loops back through authoring event sweeps.
+labelledPlanarLayerFromExactCoordinates
+  :: Ord label
+  => label
+  -> Triangulation mode vertex directed undirected face
+  -> (VertexId -> Either RegionPublicationError ExactPoint)
+  -> (FaceId -> Either RegionPublicationError label)
+  -> Either RegionPublicationError (PlanarLayer label)
+labelledPlanarLayerFromExactCoordinates outside triangulation exactPointAt labelFace = do
+  labelledComponents <-
+    traverse
+      (\(labelResult, component) -> do
+         label <- labelResult
+         boundary <-
+           first RegionBoundaryObstruction
+             (componentBoundary triangulation component)
+         (label,) <$> polygonComponentFromBoundaryCoordinates exactPointAt boundary)
+      [ pair
+      | pair@(labelResult, _) <- faceComponents triangulation labelFace
+      , labelResult /= Right outside
+      ]
+  pure (planarLayerFromAdmittedComponents outside labelledComponents)
+
+-- | Glue already-admitted, pairwise interior-disjoint components by label.
+-- Both DCEL publication and exact overlay cells reach this point only after
+-- their topology owner has proved those obligations.
+planarLayerFromAdmittedComponents
+  :: Ord label
+  => label
+  -> [(label, PolygonComponent)]
+  -> PlanarLayer label
+planarLayerFromAdmittedComponents outside labelledComponents =
+  PlanarLayer
+    outside
+    ( Map.map
+        (PlanarRegion . sort)
+        ( Map.fromListWith (<>)
+            [(label, [component]) | (label, component) <- labelledComponents]
+        )
+    )
+
+-- | Convert one already-traced resident component boundary against the exact
+-- coordinate carrier admitted for that same resident topology.
+polygonComponentFromBoundaryCoordinates
+  :: (VertexId -> Either RegionPublicationError ExactPoint)
+  -> RegionBoundary
+  -> Either RegionPublicationError PolygonComponent
+polygonComponentFromBoundaryCoordinates exactPointAt boundary = do
+  outer <- convertLoop (regionBoundaryOuterLoop boundary)
+  holes <- traverse convertLoop (regionBoundaryHoleLoops boundary)
+  pure (PolygonComponent outer (sort holes))
+ where
+  convertLoop :: BoundaryLoop -> Either RegionPublicationError ExactLoop
+  convertLoop loop = do
+    points <- traverse exactPointAt (boundaryLoopVertices loop)
+    admittedDerivedLoop points
+
+-- | Boundary descent already proves simplicity, winding, and component
+-- compatibility. Exact simplification remains necessary because the exact
+-- carrier may expose a collinearity that the embedded boundary retained.
+admittedDerivedLoop
+  :: NonEmpty ExactPoint
+  -> Either RegionPublicationError ExactLoop
+admittedDerivedLoop points = do
+  (_, simplified) <-
+    first RegionValidationObstruction
+      ( simplifyBoundaryCycle
+          RegionLoopDegenerate
+          (\previous current next ->
+             exactOrient2d previous current next == EQ
+               && exactOnClosedSegment previous next current)
+          exactOrient2d
+          id
+          (NonEmpty.toList points)
+      )
+  pure (ExactLoop (rotateCycleLeast simplified))
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Region/Types.hs b/src-dcel/Moonlight/Triangulation/Internal/Region/Types.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/Region/Types.hs
@@ -0,0 +1,97 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | The invariant-bearing exact region carriers. Public construction and
+-- validation remain in "Moonlight.Triangulation.Region"; downstream build-tier
+-- algorithms import this owner only when their algebra proves the constructors'
+-- obligations directly.
+module Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop (..)
+  , PolygonComponent (..)
+  , PlanarRegion (..)
+  , RegionPointLocation (..)
+  , PlanarLayer (..)
+  , RegionValidationError (..)
+  , RegionPublicationError (..)
+  ) where
+
+import Control.DeepSeq (NFData)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Map.Strict (Map)
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , SegmentRelation
+  )
+import Moonlight.Triangulation.FloodFillIterator (BoundaryObstruction)
+import Moonlight.Triangulation.Handles.HandleDefs (FaceId, VertexId)
+import Moonlight.Triangulation.Internal.ExactSegmentEvents
+  ( ExactSegmentEventObstruction
+  )
+import Moonlight.Triangulation.Internal.Types (PointValidationError)
+
+-- | One admitted, simple exact cycle. Its first point is the least exact point
+-- on the cycle, so equality does not retain an authoring rotation.
+newtype ExactLoop = ExactLoop (NonEmpty ExactPoint)
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | One connected polygonal component: a counter-clockwise outer cycle and
+-- zero or more clockwise holes.
+data PolygonComponent = PolygonComponent
+  { polygonOuterLoop :: !ExactLoop
+  , polygonHoleLoops :: ![ExactLoop]
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A finite union of components with pairwise-disjoint interiors.
+newtype PlanarRegion = PlanarRegion [PolygonComponent]
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Exact point position relative to a closed region.
+data RegionPointLocation
+  = RegionExterior
+  | RegionOnBoundary
+  | RegionInterior
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A finite labelled planar layer. The outside label is implicit and may not
+-- also own a bounded region.
+data PlanarLayer label = PlanarLayer
+  { planarLayerOutsideLabel :: !label
+  , planarLayerRegions :: !(Map label PlanarRegion)
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Typed authoring obstructions. Indices are cycle/component positions in the
+-- submitted value and every geometric relation preserves its exact witness.
+data RegionValidationError
+  = RegionLoopDegenerate ![ExactPoint]
+  | RegionLoopSelfRelation !Int !Int !SegmentRelation
+  | RegionOuterLoopWinding !Ordering
+  | RegionHoleLoopWinding !Int !Ordering
+  | RegionHoleLocation !Int !RegionPointLocation
+  | RegionBoundaryRelation !Int !Int !SegmentRelation
+  | RegionComponentInteriorOverlap !Int !Int
+  | RegionLayerInteriorOverlap !Int !Int
+  | RegionOutsideLabelUsed
+  | RegionSegmentEventsInvalid !ExactSegmentEventObstruction
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Typed failures while publishing an existing triangulation as exact region
+-- values. The handle is retained when a resident coordinate is inadmissible.
+data RegionPublicationError
+  = RegionBoundaryObstruction !BoundaryObstruction
+  | RegionCoordinateObstruction !VertexId !PointValidationError
+  | RegionCoordinateMissing !VertexId
+  | RegionFaceLabelMissing !FaceId
+  | RegionValidationObstruction !RegionValidationError
+  | RegionUnboundedSelection
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Representation.hs b/src-dcel/Moonlight/Triangulation/Internal/Representation.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/Representation.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/Representation.hs
@@ -22,6 +22,8 @@
   , mapDirectedEdges
   , mapUndirectedEdges
   , mapFaces
+  , imapUndirectedEdges
+  , imapFaces
   , DelaunayTriangulation
   , ConstrainedDelaunayTriangulation
   , BuildResult (..)
@@ -37,7 +39,11 @@
 import Data.Traversable (foldMapDefault)
 import qualified Data.Vector as V
 import Data.Word (Word8, Word32)
-import Moonlight.Triangulation.Handles.HandleDefs (FaceId, UndirectedEdgeId, VertexId)
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( FaceId (..)
+  , UndirectedEdgeId (..)
+  , VertexId (..)
+  )
 import Moonlight.Triangulation.Internal.BoxedPaged (BoxedPaged, boxedFromVector, boxedToVector)
 import Moonlight.Triangulation.Internal.Paged (Paged)
 import Moonlight.Triangulation.Internal.PointIndex (PointIndex)
@@ -292,6 +298,42 @@
   -> Triangulation mode vertex' directed undirected face
 mapVertices f triangulation =
   triangulation{triVertexData = fmap f (triVertexData triangulation)}
+
+-- | Materialize every resident undirected-edge annotation in handle order
+-- while installing the declared fallback for edges created by a later edit.
+imapUndirectedEdges
+  :: undirected'
+  -> (UndirectedEdgeId -> undirected -> undirected')
+  -> Triangulation mode vertex directed undirected face
+  -> Triangulation mode vertex directed undirected' face
+imapUndirectedEdges fallback relabel triangulation =
+  triangulation
+    { triUndirectedData =
+        boxedFromVector (Just fallback)
+          (V.imap (\index -> relabel (UndirectedEdgeId (fromIntegral index))) payloads)
+    , triElementDefaults = defaults{defaultUndirectedEdgeData = fallback}
+    }
+ where
+  payloads = boxedToVector (triUndirectedData triangulation)
+  defaults = triElementDefaults triangulation
+
+-- | Materialize every resident face annotation in handle order while
+-- installing the declared fallback for faces created by a later edit.
+imapFaces
+  :: face'
+  -> (FaceId -> face -> face')
+  -> Triangulation mode vertex directed undirected face
+  -> Triangulation mode vertex directed undirected face'
+imapFaces fallback relabel triangulation =
+  triangulation
+    { triFaceData =
+        boxedFromVector (Just fallback)
+          (V.imap (\index -> relabel (FaceId (fromIntegral index))) payloads)
+    , triElementDefaults = defaults{defaultFaceData = fallback}
+    }
+ where
+  payloads = boxedToVector (triFaceData triangulation)
+  defaults = triElementDefaults triangulation
 
 -- | Geometry-only unconstrained Delaunay triangulation.
 type DelaunayTriangulation vertex = Triangulation 'Unconstrained vertex () () ()
diff --git a/src-dcel/Moonlight/Triangulation/Internal/SegmentRelation.hs b/src-dcel/Moonlight/Triangulation/Internal/SegmentRelation.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Internal/SegmentRelation.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | The complete closed-segment relation vocabulary and its one policy owner.
+module Moonlight.Triangulation.Internal.SegmentRelation
+  ( SegmentRelation (..)
+  , allSegmentRelations
+  , segmentRelationWith
+  ) where
+
+import Control.DeepSeq (NFData)
+import GHC.Generics (Generic)
+
+-- | The complete exact-predicate relation between two closed segments. There
+-- is one vocabulary owner; traversal and constrained-union consumers derive
+-- their booleans and obstruction policy from it rather than cloning slightly
+-- different orientation formulae.
+data SegmentRelation
+  = SegmentsDisjoint
+  | SegmentsDuplicate
+  | SegmentsShareEndpoint
+  | SegmentsProperlyCross
+  | SegmentEndpointTouchesInterior
+  | SegmentsCollinearlyOverlap
+  deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Every segment relation in constructor order.
+allSegmentRelations :: [SegmentRelation]
+allSegmentRelations = [minBound .. maxBound]
+
+-- | Classify two closed segments using the supplied point observations.
+segmentRelationWith
+  :: (point -> point -> Bool)
+  -- ^ Point equality.
+  -> (point -> point -> Ordering)
+  -- ^ Lexicographic point ordering.
+  -> (point -> point -> point -> Ordering)
+  -- ^ Orientation of an ordered triple.
+  -> (point -> point -> point -> Bool)
+  -- ^ Membership of the third point in the closed segment.
+  -> point
+  -> point
+  -> point
+  -> point
+  -> SegmentRelation
+segmentRelationWith equalPoint comparePoint orientation onSegment a b c d
+  | sameUndirectedSegment = SegmentsDuplicate
+  | sharesEndpoint = SegmentsShareEndpoint
+  | opposite abC abD && opposite cdA cdB = SegmentsProperlyCross
+  | abC == EQ && abD == EQ && cdA == EQ && cdB == EQ = collinearRelation
+  | endpointTouches = SegmentEndpointTouchesInterior
+  | otherwise = SegmentsDisjoint
+ where
+  !abC = orientation a b c
+  !abD = orientation a b d
+  !cdA = orientation c d a
+  !cdB = orientation c d b
+  sameUndirectedSegment =
+    (equalPoint a c && equalPoint b d)
+      || (equalPoint a d && equalPoint b c)
+  sharesEndpoint =
+    equalPoint a c
+      || equalPoint a d
+      || equalPoint b c
+      || equalPoint b d
+  endpointTouches =
+    (abC == EQ && onSegment a b c)
+      || (abD == EQ && onSegment a b d)
+      || (cdA == EQ && onSegment c d a)
+      || (cdB == EQ && onSegment c d b)
+  collinearRelation =
+    let !overlapLower = maximumPoint (minimumPoint a b) (minimumPoint c d)
+        !overlapUpper = minimumPoint (maximumPoint a b) (maximumPoint c d)
+     in case comparePoint overlapLower overlapUpper of
+          LT -> SegmentsCollinearlyOverlap
+          EQ -> SegmentEndpointTouchesInterior
+          GT -> SegmentsDisjoint
+  opposite left right =
+    (left == LT && right == GT) || (left == GT && right == LT)
+  minimumPoint left right =
+    case comparePoint left right of
+      GT -> right
+      _ -> left
+  maximumPoint left right =
+    case comparePoint left right of
+      LT -> right
+      _ -> left
+{-# INLINE segmentRelationWith #-}
diff --git a/src-dcel/Moonlight/Triangulation/Internal/Types.hs b/src-dcel/Moonlight/Triangulation/Internal/Types.hs
--- a/src-dcel/Moonlight/Triangulation/Internal/Types.hs
+++ b/src-dcel/Moonlight/Triangulation/Internal/Types.hs
@@ -260,6 +260,7 @@
       !VertexId
       !DirectedEdgeId
       {-# UNPACK #-} !Int
+  | CircleSweepRequiresDenseStorage
   | CircleSweepHullEmpty
   | OuterCycleDidNotTerminate
       !DirectedEdgeId
diff --git a/src-dcel/Moonlight/Triangulation/Math.hs b/src-dcel/Moonlight/Triangulation/Math.hs
--- a/src-dcel/Moonlight/Triangulation/Math.hs
+++ b/src-dcel/Moonlight/Triangulation/Math.hs
@@ -1,7 +1,4 @@
 {-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DerivingStrategies #-}
 
 -- | Robust planar predicates and derived Euclidean constructions.
 module Moonlight.Triangulation.Math
@@ -39,17 +36,21 @@
   , isFinite
   ) where
 
-import Control.DeepSeq (NFData)
-import GHC.Generics (Generic)
 import Moonlight.Triangulation.Internal.Dyadic
   ( exactBarycentricDeterminants
   , exactDiametralDot
   , integerRatioToDouble
   )
+import Moonlight.Triangulation.Internal.SegmentRelation
+  ( SegmentRelation (..)
+  , allSegmentRelations
+  , segmentRelationWith
+  )
 import Moonlight.Triangulation.LineSideInfo (LineSideInfo, fromOrdering)
 import Moonlight.Triangulation.Scalar
   ( canonicalScalarZero
   , inCircleCoordinates
+  , isFinite
   , maximumAllowedCoordinate
   , minimumAllowedCoordinate
   , orient2dCoordinates
@@ -63,24 +64,6 @@
   , QueryPoint (..)
   )
 
--- | The complete exact-predicate relation between two closed segments. There
--- is one vocabulary owner; traversal and constrained-union consumers derive
--- their booleans and obstruction policy from it rather than cloning slightly
--- different orientation formulae.
-data SegmentRelation
-  = SegmentsDisjoint
-  | SegmentsDuplicate
-  | SegmentsShareEndpoint
-  | SegmentsProperlyCross
-  | SegmentEndpointTouchesInterior
-  | SegmentsCollinearlyOverlap
-  deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)
-  deriving anyclass (NFData)
-
--- | Every segment relation in constructor order.
-allSegmentRelations :: [SegmentRelation]
-allSegmentRelations = [minBound .. maxBound]
-
 -- | Exact relation between two closed segments.
 segmentRelation
   :: Point
@@ -88,34 +71,8 @@
   -> Point
   -> Point
   -> SegmentRelation
-segmentRelation a b c d
-  | sameUndirectedSegment = SegmentsDuplicate
-  | sharesEndpoint = SegmentsShareEndpoint
-  | opposite abC abD && opposite cdA cdB = SegmentsProperlyCross
-  | abC == EQ && abD == EQ && cdA == EQ && cdB == EQ = collinearRelation
-  | endpointTouches = SegmentEndpointTouchesInterior
-  | otherwise = SegmentsDisjoint
- where
-  !abC = orient2d a b c
-  !abD = orient2d a b d
-  !cdA = orient2d c d a
-  !cdB = orient2d c d b
-  sameUndirectedSegment = (a == c && b == d) || (a == d && b == c)
-  sharesEndpoint = a == c || a == d || b == c || b == d
-  endpointTouches =
-    (abC == EQ && onClosedSegment a b c)
-      || (abD == EQ && onClosedSegment a b d)
-      || (cdA == EQ && onClosedSegment c d a)
-      || (cdB == EQ && onClosedSegment c d b)
-  collinearRelation =
-    let !overlapLower = max (min a b) (min c d)
-        !overlapUpper = min (max a b) (max c d)
-     in case compare overlapLower overlapUpper of
-          LT -> SegmentsCollinearlyOverlap
-          EQ -> SegmentEndpointTouchesInterior
-          GT -> SegmentsDisjoint
-  opposite left right =
-    (left == LT && right == GT) || (left == GT && right == LT)
+segmentRelation a b c d =
+  segmentRelationWith (==) compare orient2d onClosedSegment a b c d
 
 -- | Whether two closed segments share any point.
 segmentsIntersect
@@ -191,16 +148,6 @@
 canonicalCoordinate :: Double -> Double
 canonicalCoordinate = canonicalScalarZero
 {-# INLINE canonicalCoordinate #-}
-
--- base's 'isNaN' and 'isInfinite' are FFI calls in this GHC (static
--- ccall to isDoubleNaN/isDoubleInfinite), and a circumcentre pays four of
--- them per call. @value - value == 0@ is the same predicate in pure Double
--- arithmetic: finite values subtract to zero, while NaN and the infinities
--- subtract to NaN, which never compares equal.
--- | Whether a scalar is neither infinite nor NaN.
-isFinite :: Double -> Bool
-isFinite value = value - value == 0
-{-# INLINE isFinite #-}
 
 -- | Fast approximate signed orientation determinant.
 orientDetApprox :: Point -> Point -> Point -> Double
diff --git a/src-dcel/Moonlight/Triangulation/Region.hs b/src-dcel/Moonlight/Triangulation/Region.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Region.hs
@@ -0,0 +1,426 @@
+-- | Exact polygon authoring and grouped publication through the resident DCEL
+-- region traversal. Construction validates topology once; downstream overlay
+-- consumes only admitted layers.
+module Moonlight.Triangulation.Region
+  ( ExactLoop
+  , exactLoop
+  , exactLoopPoints
+  , PolygonComponent
+  , polygonComponent
+  , polygonOuterLoop
+  , polygonHoleLoops
+  , PlanarRegion
+  , planarRegion
+  , planarRegionComponents
+  , emptyPlanarRegion
+  , RegionPointLocation (..)
+  , regionPointLocation
+  , PlanarLayer
+  , planarLayerOutsideLabel
+  , planarLayerRegions
+  , planarLayer
+  , planarLayerLabelAt
+  , RegionValidationError (..)
+  , RegionPublicationError (..)
+  , labelledPlanarLayer
+  ) where
+
+import Data.Bifunctor (first)
+import Data.Foldable (traverse_)
+import Data.List (sort)
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict (Map)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Vector as V
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , ExactSegment
+  , SegmentRelation (..)
+  , exactOnClosedSegment
+  , exactOrient2d
+  , exactPointCross
+  , exactPointCoordinates
+  , exactSegment
+  , exactSegmentEndpoints
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle
+  ( cyclePairs
+  , rotateCycleLeast
+  , simplifyBoundaryCycle
+  )
+import Moonlight.Triangulation.Internal.ExactSegmentEvents
+  ( ExactSegmentEventObstruction (..)
+  , ExactSweepSegmentId (..)
+  , exactSegmentEventPlan
+  , exactSegmentRelationMap
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( exactSignum )
+import Moonlight.Triangulation.Internal.Region.Publication (labelledPlanarLayer)
+import Moonlight.Triangulation.Internal.Region.Bounds
+  ( boundsOverlap
+  , componentBounds
+  , exactLoopBounds
+  , overlappingOptionalPairs
+  , overlappingPairs
+  , overlappingPairsBetween
+  , pointInBounds
+  , regionBounds
+  )
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop (..)
+  , PlanarLayer (..)
+  , PlanarRegion (..)
+  , PolygonComponent (..)
+  , RegionPointLocation (..)
+  , RegionPublicationError (..)
+  , RegionValidationError (..)
+  )
+
+data LoopRelationWitness = LoopRelationWitness
+  !Int
+  !Int
+  !ExactSegment
+  !ExactSegment
+  !SegmentRelation
+
+-- | Admit and canonicalize one simple exact cycle.
+exactLoop :: NonEmpty ExactPoint -> Either RegionValidationError ExactLoop
+exactLoop submitted = do
+  let withoutRepeatedTerminal = removeRepeatedTerminal (NonEmpty.toList submitted)
+  (_, simplified) <-
+    simplifyBoundaryCycle
+      RegionLoopDegenerate
+      exactRedundant
+      exactOrient2d
+      id
+      withoutRepeatedTerminal
+  let canonical = rotateCycleLeast simplified
+  validateSimpleLoop canonical
+  pure (ExactLoop canonical)
+
+-- | Read the canonical cycle points.
+exactLoopPoints :: ExactLoop -> NonEmpty ExactPoint
+exactLoopPoints (ExactLoop points) = points
+
+-- | Admit one component after checking orientation, containment, boundary
+-- relations, and pairwise-disjoint hole interiors.
+polygonComponent
+  :: ExactLoop
+  -> [ExactLoop]
+  -> Either RegionValidationError PolygonComponent
+polygonComponent outer holes = do
+  let outerWinding = loopWinding outer
+  if outerWinding == GT
+    then Right ()
+    else Left (RegionOuterLoopWinding outerWinding)
+  traverse_ validateHoleWinding (zip [0 ..] holes)
+  traverse_ validateHole (zip [0 ..] holes)
+  traverse_
+    validateHolePair
+    (overlappingPairs (exactLoopBounds . snd) (zip [0 ..] holes))
+  pure (PolygonComponent outer (sort holes))
+ where
+  validateHoleWinding (holeIndex, hole) =
+    let winding = loopWinding hole
+     in if winding == LT
+          then Right ()
+          else Left (RegionHoleLoopWinding holeIndex winding)
+  validateHole (holeIndex, hole) = do
+    relations <- crossLoopRelations outer hole
+    case relations of
+      LoopRelationWitness outerEdge holeEdge _ _ relation : _ ->
+        Left (RegionBoundaryRelation outerEdge holeEdge relation)
+      [] -> Right ()
+    let location = pointLocationInLoop outer (firstExactLoopPoint hole)
+    if location == RegionInterior
+      then Right ()
+      else Left (RegionHoleLocation holeIndex location)
+  validateHolePair ((leftIndex, left), (rightIndex, right)) =
+    do
+      relations <- crossLoopRelations left right
+      case relations of
+        LoopRelationWitness _ _ _ _ relation : _ ->
+          Left (RegionBoundaryRelation leftIndex rightIndex relation)
+        []
+          | loopContainsInteriorPoint left right || loopContainsInteriorPoint right left ->
+              Left (RegionComponentInteriorOverlap leftIndex rightIndex)
+          | otherwise -> Right ()
+
+-- | Admit a finite region after checking that component interiors do not
+-- overlap. Boundary-only contact remains lawful.
+planarRegion
+  :: [PolygonComponent]
+  -> Either RegionValidationError PlanarRegion
+planarRegion components = do
+  traverse_
+    validatePair
+    (overlappingPairs (componentBounds . snd) (zip [0 ..] components))
+  pure (PlanarRegion (sort components))
+ where
+  validatePair ((leftIndex, left), (rightIndex, right)) = do
+    overlaps <- componentInteriorsOverlap left right
+    if overlaps
+      then Left (RegionComponentInteriorOverlap leftIndex rightIndex)
+      else Right ()
+
+-- | Observe the canonically ordered components.
+planarRegionComponents :: PlanarRegion -> [PolygonComponent]
+planarRegionComponents (PlanarRegion components) = components
+
+-- | The empty finite region.
+emptyPlanarRegion :: PlanarRegion
+emptyPlanarRegion = PlanarRegion []
+
+-- | Locate an exact point against all components. Boundary membership has
+-- priority over interior membership.
+regionPointLocation :: PlanarRegion -> ExactPoint -> RegionPointLocation
+regionPointLocation (PlanarRegion components) query =
+  foldr
+    (combineLocation . (`componentPointLocation` query))
+    RegionExterior
+    components
+
+-- | Admit one labelled layer. Different labels may touch but their interiors
+-- may not overlap, and the outside label cannot also name a bounded region.
+planarLayer
+  :: Ord label
+  => label
+  -> Map label PlanarRegion
+  -> Either RegionValidationError (PlanarLayer label)
+planarLayer outside regions
+  | Map.member outside regions = Left RegionOutsideLabelUsed
+  | otherwise = do
+      let indexed = zip [0 ..] (Map.toAscList regions)
+      traverse_
+        (\((leftIndex, (_, left)), (rightIndex, (_, right))) -> do
+           overlaps <- regionsInteriorsOverlap left right
+           if overlaps
+             then Left (RegionLayerInteriorOverlap leftIndex rightIndex)
+             else Right ())
+        (overlappingOptionalPairs (regionBounds . snd . snd) indexed)
+      pure (PlanarLayer outside regions)
+
+-- | Label an exact point known to lie in a relatively open two-cell. Boundary
+-- points conservatively retain the outside label.
+planarLayerLabelAt :: PlanarLayer label -> ExactPoint -> label
+planarLayerLabelAt layer query =
+  case
+    [ label
+    | (label, region) <- Map.toAscList (planarLayerRegions layer)
+    , regionPointLocation region query == RegionInterior
+    ] of
+    label : _ -> label
+    [] -> planarLayerOutsideLabel layer
+
+removeRepeatedTerminal :: Eq value => [value] -> [value]
+removeRepeatedTerminal values =
+  case values of
+    [] -> []
+    firstValue : remaining ->
+      case reverse remaining of
+        finalValue : reversedMiddle
+          | firstValue == finalValue -> firstValue : reverse reversedMiddle
+        _ -> values
+
+exactRedundant :: ExactPoint -> ExactPoint -> ExactPoint -> Bool
+exactRedundant previous current next =
+  exactOrient2d previous current next == EQ
+    && exactOnClosedSegment previous next current
+
+validateSimpleLoop :: NonEmpty ExactPoint -> Either RegionValidationError ()
+validateSimpleLoop points = do
+  segments <- loopSegments points
+  plan <- first RegionSegmentEventsInvalid (exactSegmentEventPlan segments)
+  case
+    [ (leftIndex, rightIndex, relation)
+    | ((ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex), relation) <-
+        Map.toAscList (exactSegmentRelationMap plan)
+    , not (adjacentSegment segmentCount leftIndex rightIndex && relation == SegmentsShareEndpoint)
+    ] of
+    (leftIndex, rightIndex, relation) : _ ->
+      Left (RegionLoopSelfRelation leftIndex rightIndex relation)
+    [] -> Right ()
+ where
+  segmentCount = NonEmpty.length points
+
+loopSegments
+  :: NonEmpty ExactPoint
+  -> Either RegionValidationError (V.Vector ExactSegment)
+loopSegments points =
+  V.fromList
+    <$> traverse
+      (\(from, to) ->
+         first (const (RegionLoopDegenerate (NonEmpty.toList points)))
+           (exactSegment from to))
+      (cyclePairs points)
+
+adjacentSegment :: Int -> Int -> Int -> Bool
+adjacentSegment count left right =
+  right == left + 1 || (left == 0 && right == count - 1)
+
+loopWinding :: ExactLoop -> Ordering
+loopWinding (ExactLoop points) =
+  exactSignum
+    ( foldl'
+        (\signedArea (from, to) ->
+           signedArea + exactPointCross from to)
+        0
+        (cyclePairs points)
+    )
+
+pointLocationInLoop :: ExactLoop -> ExactPoint -> RegionPointLocation
+pointLocationInLoop loop query
+  | not (pointInBounds query (exactLoopBounds loop)) = RegionExterior
+  | any (\(from, to) -> exactOnClosedSegment from to query) edges = RegionOnBoundary
+  | odd (length (filter crossesRay edges)) = RegionInterior
+  | otherwise = RegionExterior
+ where
+  edges = cyclePairs (exactLoopPoints loop)
+  (_, py) = exactPointCoordinates query
+  crossesRay (from, to) =
+    let (_, ay) = exactPointCoordinates from
+        (_, by) = exactPointCoordinates to
+        orientation = exactOrient2d from to query
+     in (ay <= py && py < by && orientation == GT)
+          || (by <= py && py < ay && orientation == LT)
+
+componentPointLocation :: PolygonComponent -> ExactPoint -> RegionPointLocation
+componentPointLocation component query =
+  case pointLocationInLoop (polygonOuterLoop component) query of
+    RegionExterior -> RegionExterior
+    RegionOnBoundary -> RegionOnBoundary
+    RegionInterior -> foldr classifyHole RegionInterior (polygonHoleLoops component)
+ where
+  classifyHole hole remaining =
+    case pointLocationInLoop hole query of
+      RegionExterior -> remaining
+      RegionOnBoundary -> RegionOnBoundary
+      RegionInterior -> RegionExterior
+
+crossLoopRelations
+  :: ExactLoop
+  -> ExactLoop
+  -> Either RegionValidationError [LoopRelationWitness]
+crossLoopRelations left right
+  | not (boundsOverlap (exactLoopBounds left) (exactLoopBounds right)) = Right []
+  | otherwise = do
+      leftSegments <- loopSegments (exactLoopPoints left)
+      rightSegments <- loopSegments (exactLoopPoints right)
+      let leftCount = V.length leftSegments
+          relationWitness (leftIndex, rightIndex, relation) =
+            LoopRelationWitness leftIndex rightIndex
+              <$> requireLoopSegment leftSegments leftIndex
+              <*> requireLoopSegment rightSegments rightIndex
+              <*> pure relation
+      plan <-
+        first RegionSegmentEventsInvalid
+          (exactSegmentEventPlan (leftSegments <> rightSegments))
+      traverse relationWitness
+        [ (leftIndex, rightIndex - leftCount, relation)
+        | ((ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex), relation) <-
+            Map.toAscList (exactSegmentRelationMap plan)
+        , leftIndex < leftCount
+        , rightIndex >= leftCount
+        ]
+
+requireLoopSegment
+  :: V.Vector ExactSegment
+  -> Int
+  -> Either RegionValidationError ExactSegment
+requireLoopSegment segments index =
+  maybe
+    ( Left
+        (RegionSegmentEventsInvalid (ExactSweepSegmentMissing (ExactSweepSegmentId index)))
+    )
+    Right
+    (segments V.!? index)
+
+loopContainsInteriorPoint :: ExactLoop -> ExactLoop -> Bool
+loopContainsInteriorPoint container candidate =
+  pointLocationInLoop container (firstExactLoopPoint candidate) == RegionInterior
+
+componentInteriorsOverlap
+  :: PolygonComponent
+  -> PolygonComponent
+  -> Either RegionValidationError Bool
+componentInteriorsOverlap left right =
+  boundariesProperlyCross left right
+    >>= \crossing ->
+      pure
+        ( crossing
+            || componentContainsInteriorPoint left right
+            || componentContainsInteriorPoint right left
+        )
+
+boundariesProperlyCross
+  :: PolygonComponent
+  -> PolygonComponent
+  -> Either RegionValidationError Bool
+boundariesProperlyCross left right =
+  anyEither
+    (uncurry loopPairInteriorsOverlap)
+    ( overlappingPairsBetween
+        exactLoopBounds
+        (componentLoops left)
+        (componentLoops right)
+    )
+ where
+  componentLoops component =
+    polygonOuterLoop component : polygonHoleLoops component
+
+loopPairInteriorsOverlap
+  :: ExactLoop
+  -> ExactLoop
+  -> Either RegionValidationError Bool
+loopPairInteriorsOverlap leftLoop rightLoop = do
+  relations <- crossLoopRelations leftLoop rightLoop
+  pure (any relationOverlapsInteriors relations)
+ where
+  relationOverlapsInteriors
+    (LoopRelationWitness _ _ leftSegment rightSegment relation) =
+    case relation of
+      SegmentsProperlyCross -> True
+      SegmentsDuplicate -> collinearInteriorsCoincide leftSegment rightSegment
+      SegmentsCollinearlyOverlap -> collinearInteriorsCoincide leftSegment rightSegment
+      _ -> False
+  collinearInteriorsCoincide leftSegment rightSegment =
+    canonicalDirection leftSegment == canonicalDirection rightSegment
+  canonicalDirection segment =
+    uncurry (<=) (exactSegmentEndpoints segment)
+
+componentContainsInteriorPoint :: PolygonComponent -> PolygonComponent -> Bool
+componentContainsInteriorPoint container candidate =
+  any
+    ((== RegionInterior) . componentPointLocation container)
+    (NonEmpty.toList (exactLoopPoints (polygonOuterLoop candidate)))
+
+firstExactLoopPoint :: ExactLoop -> ExactPoint
+firstExactLoopPoint (ExactLoop (point :| _)) = point
+
+regionsInteriorsOverlap
+  :: PlanarRegion
+  -> PlanarRegion
+  -> Either RegionValidationError Bool
+regionsInteriorsOverlap (PlanarRegion left) (PlanarRegion right) =
+  anyEither
+    (uncurry componentInteriorsOverlap)
+    (overlappingPairsBetween componentBounds left right)
+
+combineLocation :: RegionPointLocation -> RegionPointLocation -> RegionPointLocation
+combineLocation RegionOnBoundary _ = RegionOnBoundary
+combineLocation RegionExterior accumulated = accumulated
+combineLocation RegionInterior RegionOnBoundary = RegionOnBoundary
+combineLocation RegionInterior _ = RegionInterior
+
+anyEither
+  :: (value -> Either obstruction Bool)
+  -> [value]
+  -> Either obstruction Bool
+anyEither predicate =
+  foldr
+    (\value remaining -> do
+       matches <- predicate value
+       if matches then Right True else remaining)
+    (Right False)
diff --git a/src-dcel/Moonlight/Triangulation/Valuation.hs b/src-dcel/Moonlight/Triangulation/Valuation.hs
new file mode 100644
--- /dev/null
+++ b/src-dcel/Moonlight/Triangulation/Valuation.hs
@@ -0,0 +1,645 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs #-}
+
+-- | Intrinsic valuations of exact closed cell selections and admitted planar
+-- regions. Euler characteristic and area remain exact; Euclidean length is an
+-- exact radical expression accompanied by outward-rounded binary64 bounds.
+module Moonlight.Triangulation.Valuation
+  ( EulerCharacteristic
+  , eulerCharacteristicValue
+  , ExactArea
+  , exactAreaValue
+  , ExactLengthTerm
+  , lengthCoefficient
+  , squaredLength
+  , ExactLengthExpression
+  , exactLengthTerms
+  , CertifiedInterval (..)
+  , ExactLengthMeasurement
+  , exactLengthExpression
+  , exactLengthBounds
+  , PlanarValuations
+  , valuationEuler
+  , valuationArea
+  , valuationIntrinsic1
+  , ValuationError (..)
+  , cellValuations
+  , regionValuations
+  , cellSetPerimeter
+  , regionPerimeter
+  ) where
+
+import Control.DeepSeq (NFData)
+import Data.Bifunctor (first)
+import Data.Bits (shiftL)
+import Data.Foldable (foldlM)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.IntMap.Strict as IntMap
+import qualified Data.IntSet as IntSet
+import qualified Data.Map.Strict as Map
+import Data.Maybe (catMaybes)
+import qualified Data.Ratio as Ratio
+import qualified Data.Set as Set
+import qualified Data.Vector as V
+import GHC.Float (castDoubleToWord64, castWord64ToDouble)
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Dcel
+  ( faceVertices
+  , incidentFace
+  , undirectedEndpoints
+  )
+import Moonlight.Triangulation.Exact
+  ( ExactGeometryError
+  , ExactPoint
+  , ExactSegment
+  , exactOnClosedSegment
+  , exactPointCross
+  , exactPointCoordinates
+  , exactSegment
+  , exactSegmentEndpoints
+  )
+import Moonlight.Triangulation.Handles.HandleDefs
+  ( FaceId (..)
+  , UndirectedEdgeId (..)
+  , VertexId (..)
+  , directedPair
+  )
+import Moonlight.Triangulation.Internal.CellSet
+  ( ExactCellSet (..)
+  , exactCellSetIsFaceClosure
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle
+  ( consecutivePairs
+  , cyclePairs
+  , orderedPair
+  )
+import Moonlight.Triangulation.Internal.Dyadic (integerBitLength)
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational
+  , exactRationalDenominator
+  , exactRationalFromDyadic
+  , exactRationalFromFiniteDouble
+  , exactRationalIsZero
+  , exactRationalNumerator
+  , exactSignum
+  )
+import Moonlight.Triangulation.Internal.ExactSegmentEvents
+  ( ExactSegmentEvent (..)
+  , ExactSegmentEventObstruction
+  , ExactSweepSegmentId (..)
+  , exactSegmentEventPlan
+  , exactSegmentEvents
+  , exactSegmentSplitPoints
+  )
+import Moonlight.Triangulation.Internal.Region.Types
+  ( ExactLoop (..)
+  , PlanarRegion (..)
+  , PolygonComponent (..)
+  )
+import Moonlight.Triangulation.Internal.Region.Bounds
+  ( ExactBounds
+  , componentBounds
+  , overlappingPredecessors
+  )
+import Moonlight.Triangulation.Internal.Representation (Triangulation)
+
+newtype EulerCharacteristic = EulerCharacteristic Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+eulerCharacteristicValue :: EulerCharacteristic -> Int
+eulerCharacteristicValue (EulerCharacteristic value) = value
+
+newtype ExactArea = ExactArea ExactRational
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+exactAreaValue :: ExactArea -> ExactRational
+exactAreaValue (ExactArea value) = value
+
+data ExactLengthTerm = ExactLengthTerm
+  { lengthCoefficient :: !ExactRational
+  , squaredLength :: !ExactRational
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A normalized sum of rational coefficients times square roots of rational
+-- squared lengths. It intentionally has no 'Eq' instance: syntactic radical
+-- normalization is not algebraic-number equality.
+newtype ExactLengthExpression = ExactLengthExpression [ExactLengthTerm]
+  deriving stock (Show, Generic)
+  deriving anyclass (NFData)
+
+exactLengthTerms :: ExactLengthExpression -> [ExactLengthTerm]
+exactLengthTerms (ExactLengthExpression terms) = terms
+
+data CertifiedInterval = CertifiedInterval
+  { intervalLower :: !Double
+  , intervalUpper :: !Double
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+data ExactLengthMeasurement = ExactLengthMeasurement
+  { exactLengthExpression :: !ExactLengthExpression
+  , exactLengthBounds :: !CertifiedInterval
+  }
+  deriving stock (Show, Generic)
+  deriving anyclass (NFData)
+
+data PlanarValuations = PlanarValuations
+  { valuationEuler :: !EulerCharacteristic
+  , valuationArea :: !ExactArea
+  , valuationIntrinsic1 :: !ExactLengthMeasurement
+  }
+  deriving stock (Show, Generic)
+  deriving anyclass (NFData)
+
+data ValuationError
+  = ValuationCoordinateMissing !VertexId
+  | ValuationFaceArity !FaceId !Int
+  | ValuationInvalidRegionSegment !ExactGeometryError
+  | ValuationSegmentEventsInvalid !ExactSegmentEventObstruction
+  | ValuationSegmentMissing !ExactSweepSegmentId
+  | ValuationBoundaryMultiplicity !ExactPoint !ExactPoint !Int
+  | ValuationNegativeSquaredLength !ExactRational
+  | ValuationCellSetNotPureRegion
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+cellValuations :: ExactCellSet -> Either ValuationError PlanarValuations
+cellValuations (ExactCellSet triangulation points selectedEdges selectedFaces) = do
+  faceDoubleAreas <-
+    traverse
+      (cellFaceDoubleArea triangulation points . FaceId . fromIntegral)
+      (IntSet.toAscList selectedFaces)
+  edgeContributions <-
+    traverse
+      ( cellEdgeLengthContribution triangulation points selectedFaces
+          . UndirectedEdgeId
+          . fromIntegral
+      )
+      (IntSet.toAscList selectedEdges)
+  assembleValuations
+    (IntMap.size points - IntSet.size selectedEdges + IntSet.size selectedFaces)
+    (foldl' (+) 0 faceDoubleAreas)
+    (normalizeLengthContributions id edgeContributions)
+
+regionValuations :: PlanarRegion -> Either ValuationError PlanarValuations
+regionValuations (PlanarRegion components) = do
+  componentBoundaries <- traverse componentBoundaryData components
+  let boundaryCover =
+        overlappingPredecessors componentBoundaryBounds componentBoundaries
+      hasPotentialBoundaryContacts = any (not . null . snd) boundaryCover
+  euler <- regionEuler boundaryCover
+  boundaryAtoms <-
+    normalizedRegionBoundaryAtoms
+      hasPotentialBoundaryContacts
+      componentBoundaries
+  let doubleArea =
+        foldl'
+          (\area component -> area + componentDoubleArea component)
+          0
+          components
+  assembleValuations
+    euler
+    doubleArea
+    ( normalizeLengthContributions
+        (\(from, to) -> (oneHalf, segmentSquaredLength from to))
+        boundaryAtoms
+    )
+
+assembleValuations
+  :: Int
+  -> ExactRational
+  -> ExactLengthExpression
+  -> Either ValuationError PlanarValuations
+assembleValuations euler doubleArea lengthExpression =
+  PlanarValuations (EulerCharacteristic euler) (ExactArea (oneHalf * doubleArea))
+    <$> measureLength lengthExpression
+cellSetPerimeter
+  :: ExactCellSet
+  -> Either ValuationError ExactLengthMeasurement
+cellSetPerimeter cellSet
+  | exactCellSetIsFaceClosure cellSet =
+      cellValuations cellSet >>= conventionalPerimeter
+  | otherwise = Left ValuationCellSetNotPureRegion
+
+regionPerimeter
+  :: PlanarRegion
+  -> Either ValuationError ExactLengthMeasurement
+regionPerimeter region = regionValuations region >>= conventionalPerimeter
+
+conventionalPerimeter
+  :: PlanarValuations
+  -> Either ValuationError ExactLengthMeasurement
+conventionalPerimeter valuations =
+  measureLength
+    (scaleLengthExpression 2 (exactLengthExpression (valuationIntrinsic1 valuations)))
+
+cellFaceDoubleArea
+  :: Triangulation mode vertex directed undirected face
+  -> IntMap.IntMap ExactPoint
+  -> FaceId
+  -> Either ValuationError ExactRational
+cellFaceDoubleArea triangulation points face =
+  case faceVertices triangulation face of
+    [firstVertex, secondVertex, thirdVertex] -> do
+      firstPoint <- cellPoint points firstVertex
+      secondPoint <- cellPoint points secondVertex
+      thirdPoint <- cellPoint points thirdVertex
+      pure (triangleDoubleArea firstPoint secondPoint thirdPoint)
+    vertices -> Left (ValuationFaceArity face (length vertices))
+
+cellEdgeLengthContribution
+  :: Triangulation mode vertex directed undirected face
+  -> IntMap.IntMap ExactPoint
+  -> IntSet.IntSet
+  -> UndirectedEdgeId
+  -> Either ValuationError (ExactRational, ExactRational)
+cellEdgeLengthContribution triangulation points selectedFaces edge = do
+  let (fromVertex, toVertex) = undirectedEndpoints triangulation edge
+      (forward, backward) = directedPair edge
+      selected face =
+        let FaceId raw = face
+         in IntSet.member (fromIntegral raw) selectedFaces
+      coefficient = case (selected (incidentFace triangulation forward), selected (incidentFace triangulation backward)) of
+        (False, False) -> 1
+        (True, True) -> 0
+        _ -> oneHalf
+  from <- cellPoint points fromVertex
+  to <- cellPoint points toVertex
+  pure (coefficient, segmentSquaredLength from to)
+
+cellPoint
+  :: IntMap.IntMap ExactPoint
+  -> VertexId
+  -> Either ValuationError ExactPoint
+cellPoint points vertex@(VertexId raw) =
+  maybe
+    (Left (ValuationCoordinateMissing vertex))
+    Right
+    (IntMap.lookup (fromIntegral raw) points)
+
+triangleDoubleArea :: ExactPoint -> ExactPoint -> ExactPoint -> ExactRational
+triangleDoubleArea firstPoint secondPoint thirdPoint =
+  exactPointCross firstPoint secondPoint
+    + exactPointCross secondPoint thirdPoint
+    + exactPointCross thirdPoint firstPoint
+
+componentDoubleArea :: PolygonComponent -> ExactRational
+componentDoubleArea component =
+  foldl'
+    (\area loop -> area + loopDoubleArea loop)
+    0
+    (polygonOuterLoop component : polygonHoleLoops component)
+
+loopDoubleArea :: ExactLoop -> ExactRational
+loopDoubleArea (ExactLoop points) =
+  foldl'
+    (\area (from, to) -> area + exactPointCross from to)
+    0
+    (cyclePairs points)
+
+segmentSquaredLength :: ExactPoint -> ExactPoint -> ExactRational
+segmentSquaredLength from to =
+  let (fromX, fromY) = exactPointCoordinates from
+      (toX, toY) = exactPointCoordinates to
+      deltaX = toX - fromX
+      deltaY = toY - fromY
+   in deltaX * deltaX + deltaY * deltaY
+
+normalizeLengthContributions
+  :: Foldable collection
+  => (value -> (ExactRational, ExactRational))
+  -> collection value
+  -> ExactLengthExpression
+normalizeLengthContributions contribution contributions =
+  ExactLengthExpression
+    [ ExactLengthTerm coefficient square
+    | (square, coefficient) <- Map.toAscList coefficientsBySquare
+    , not (exactRationalIsZero coefficient)
+    ]
+ where
+  coefficientsBySquare =
+    foldl' accumulateContribution Map.empty contributions
+  accumulateContribution coefficients value =
+    case contribution value of
+      (coefficient, square)
+        | exactRationalIsZero coefficient -> coefficients
+        | otherwise -> Map.insertWith (+) square coefficient coefficients
+
+scaleLengthExpression
+  :: Integer
+  -> ExactLengthExpression
+  -> ExactLengthExpression
+scaleLengthExpression scalar (ExactLengthExpression terms) =
+  let exactScalar = fromInteger scalar
+   in ExactLengthExpression
+        [ term
+            { lengthCoefficient =
+                exactScalar * lengthCoefficient term
+            }
+        | term <- terms
+        ]
+
+measureLength
+  :: ExactLengthExpression
+  -> Either ValuationError ExactLengthMeasurement
+measureLength expression@(ExactLengthExpression terms) = do
+  (lower, upper) <-
+    foldlM
+      addTermBounds
+      (0, 0)
+      terms
+  pure
+    ExactLengthMeasurement
+      { exactLengthExpression = expression
+      , exactLengthBounds =
+          CertifiedInterval
+            { intervalLower = directedLowerDouble lower
+            , intervalUpper = directedUpperDouble upper
+            }
+      }
+ where
+  addTermBounds (lowerTotal, upperTotal) term = do
+    (lowerRoot, upperRoot) <- exactSquareRootBounds (squaredLength term)
+    let coefficient = lengthCoefficient term
+    pure
+      ( lowerTotal + coefficient * lowerRoot
+      , upperTotal + coefficient * upperRoot
+      )
+
+exactSquareRootBounds
+  :: ExactRational
+  -> Either ValuationError (ExactRational, ExactRational)
+exactSquareRootBounds value =
+  case exactSignum value of
+    LT -> Left (ValuationNegativeSquaredLength value)
+    _ ->
+      let numerator = exactRationalNumerator value
+          denominator = exactRationalDenominator value
+          scale = 1 `shiftL` radicalPrecisionBits
+          scaledNumerator = numerator * scale * scale
+          root = integerSquareRoot (scaledNumerator `div` denominator)
+          exact = root * root * denominator == scaledNumerator
+          dyadicPower = negate radicalPrecisionBits
+       in Right
+            ( exactRationalFromDyadic root dyadicPower
+            , exactRationalFromDyadic (if exact then root else root + 1) dyadicPower
+            )
+
+radicalPrecisionBits :: Int
+radicalPrecisionBits = 128
+
+integerSquareRoot :: Integer -> Integer
+integerSquareRoot value
+  | value < 2 = value
+  | otherwise = descend initial
+ where
+  initial = 1 `shiftL` ((integerBitLength value + 1) `div` 2)
+  descend estimate =
+    let refined = (estimate + value `div` estimate) `div` 2
+     in if refined >= estimate then estimate else descend refined
+
+directedLowerDouble :: ExactRational -> Double
+directedLowerDouble value =
+  let candidate = rationalToDouble value
+   in if isInfinite candidate
+        then maximumFiniteDouble
+        else
+          if exactRationalFromFiniteDouble candidate <= value
+            then candidate
+            else previousPositiveDouble candidate
+
+directedUpperDouble :: ExactRational -> Double
+directedUpperDouble value =
+  let candidate = rationalToDouble value
+   in if isInfinite candidate
+        || exactRationalFromFiniteDouble candidate >= value
+        then candidate
+        else nextPositiveDouble candidate
+
+rationalToDouble :: ExactRational -> Double
+rationalToDouble value =
+  fromRational
+    ( exactRationalNumerator value
+        Ratio.% exactRationalDenominator value
+    )
+
+previousPositiveDouble :: Double -> Double
+previousPositiveDouble value
+  | value <= 0 = 0
+  | otherwise = castWord64ToDouble (castDoubleToWord64 value - 1)
+
+nextPositiveDouble :: Double -> Double
+nextPositiveDouble value
+  | value == 0 = castWord64ToDouble 1
+  | otherwise = castWord64ToDouble (castDoubleToWord64 value + 1)
+
+maximumFiniteDouble :: Double
+maximumFiniteDouble = castWord64ToDouble 0x7fefffffffffffff
+
+data ComponentBoundaryData = ComponentBoundaryData
+  { componentBoundaryEuler :: !Int
+  , componentBoundaryBounds :: !ExactBounds
+  , componentBoundarySegments :: !(V.Vector ExactSegment)
+  }
+
+componentBoundaryData
+  :: PolygonComponent
+  -> Either ValuationError ComponentBoundaryData
+componentBoundaryData component = do
+  segments <-
+    V.fromList
+      <$> traverse
+        admittedSegment
+        ( concatMap
+            (cyclePairs . loopPoints)
+            (polygonOuterLoop component : polygonHoleLoops component)
+        )
+  pure
+    ComponentBoundaryData
+      { componentBoundaryEuler = 1 - length (polygonHoleLoops component)
+      , componentBoundaryBounds = componentBounds component
+      , componentBoundarySegments = segments
+      }
+
+regionEuler
+  :: [(ComponentBoundaryData, [ComponentBoundaryData])]
+  -> Either ValuationError Int
+regionEuler = foldlM attachComponent 0
+ where
+  attachComponent accumulatedEuler (current, priorCandidates) = do
+    let priorSegments = V.concat (map componentBoundarySegments priorCandidates)
+    intersectionEuler <-
+      boundaryIntersectionEuler
+        (componentBoundarySegments current)
+        priorSegments
+    pure
+      ( accumulatedEuler
+          + componentBoundaryEuler current
+          - intersectionEuler
+      )
+
+boundaryIntersectionEuler
+  :: V.Vector ExactSegment
+  -> V.Vector ExactSegment
+  -> Either ValuationError Int
+boundaryIntersectionEuler current prior
+  | V.null prior = Right 0
+  | otherwise = do
+      let currentCount = V.length current
+          segments = current <> prior
+      plan <- first ValuationSegmentEventsInvalid (exactSegmentEventPlan segments)
+      contacts <-
+        traverse
+          (contactFromEvent segments)
+          [ event
+          | event <- exactSegmentEvents plan
+          , crossPartition currentCount event
+          ]
+      let contactPoints =
+            Set.fromList
+              [ point
+              | ContactPoint point <- contacts
+              ]
+          intervals =
+            [ interval
+            | ContactInterval interval <- contacts
+            ]
+          allSplitPoints =
+            Set.fromList
+              ( concatMap
+                  (exactSegmentSplitPoints plan)
+                  [ ExactSweepSegmentId index
+                  | index <- [0 .. V.length segments - 1]
+                  ]
+              )
+          contactEdges =
+            Set.fromList
+              [ orderedPair from to
+              | interval <- intervals
+              , let (lower, upper) = interval
+                    points =
+                      Set.toAscList
+                        ( Set.filter
+                            (exactOnClosedSegment lower upper)
+                            allSplitPoints
+                        )
+              , (from, to) <- consecutivePairs points
+              , from /= to
+              ]
+          vertices =
+            Set.unions
+              [ contactPoints
+              , Set.fromList
+                  [ point
+                  | (from, to) <- Set.toAscList contactEdges
+                  , point <- [from, to]
+                  ]
+              ]
+      pure (Set.size vertices - Set.size contactEdges)
+
+data BoundaryContact
+  = ContactPoint !ExactPoint
+  | ContactInterval !(ExactPoint, ExactPoint)
+
+contactFromEvent
+  :: V.Vector ExactSegment
+  -> ExactSegmentEvent
+  -> Either ValuationError BoundaryContact
+contactFromEvent _ (ExactProperCrossing _ _ point) = Right (ContactPoint point)
+contactFromEvent _ (ExactEndpointTouch _ _ point) = Right (ContactPoint point)
+contactFromEvent _ (ExactSharedEndpoint _ _ point) = Right (ContactPoint point)
+contactFromEvent segments (ExactDuplicateSegments leftId _) =
+  ContactInterval . canonicalSegmentEndpoints
+    <$> requireSegment segments leftId
+contactFromEvent _ (ExactCollinearOverlap _ _ lower upper) =
+  Right (ContactInterval (orderedPair lower upper))
+
+crossPartition :: Int -> ExactSegmentEvent -> Bool
+crossPartition boundary event =
+  let (ExactSweepSegmentId left, ExactSweepSegmentId right) = eventIds event
+   in (left < boundary) /= (right < boundary)
+
+eventIds
+  :: ExactSegmentEvent
+  -> (ExactSweepSegmentId, ExactSweepSegmentId)
+eventIds (ExactProperCrossing left right _) = (left, right)
+eventIds (ExactEndpointTouch left right _) = (left, right)
+eventIds (ExactSharedEndpoint left right _) = (left, right)
+eventIds (ExactDuplicateSegments left right) = (left, right)
+eventIds (ExactCollinearOverlap left right _ _) = (left, right)
+
+normalizedRegionBoundaryAtoms
+  :: Bool
+  -> [ComponentBoundaryData]
+  -> Either ValuationError (Set.Set (ExactPoint, ExactPoint))
+normalizedRegionBoundaryAtoms hasPotentialBoundaryContacts boundaries
+  | V.null segments = Right Set.empty
+  | not hasPotentialBoundaryContacts =
+      Right
+        ( Set.fromList
+            (map canonicalSegmentEndpoints (V.toList segments))
+        )
+  | otherwise = do
+      plan <- first ValuationSegmentEventsInvalid (exactSegmentEventPlan segments)
+      let orientedAtoms =
+            concatMap
+              segmentAtoms
+              [ exactSegmentSplitPoints plan (ExactSweepSegmentId index)
+              | index <- [0 .. V.length segments - 1]
+              ]
+      traverseMultiplicity
+        (Map.toAscList (Map.fromListWith (+) orientedAtoms))
+ where
+  segments = V.concat (map componentBoundarySegments boundaries)
+  segmentAtoms :: [ExactPoint] -> [((ExactPoint, ExactPoint), Int)]
+  segmentAtoms points =
+    [ ( orderedPair firstPoint secondPoint
+      , if firstPoint <= secondPoint then 1 else -1
+      )
+    | (firstPoint, secondPoint) <- consecutivePairs points
+    , firstPoint /= secondPoint
+    ]
+  traverseMultiplicity
+    :: [((ExactPoint, ExactPoint), Int)]
+    -> Either ValuationError (Set.Set (ExactPoint, ExactPoint))
+  traverseMultiplicity entries = do
+    retained <-
+      traverse
+        (\(edge@(from, to), multiplicity) ->
+           case abs multiplicity of
+             0 -> Right Nothing
+             1 -> Right (Just edge)
+             _ -> Left (ValuationBoundaryMultiplicity from to multiplicity))
+        entries
+    pure (Set.fromList (catMaybes retained))
+
+admittedSegment
+  :: (ExactPoint, ExactPoint)
+  -> Either ValuationError ExactSegment
+admittedSegment (from, to) = first ValuationInvalidRegionSegment (exactSegment from to)
+
+requireSegment
+  :: V.Vector ExactSegment
+  -> ExactSweepSegmentId
+  -> Either ValuationError ExactSegment
+requireSegment segments segmentId@(ExactSweepSegmentId index) =
+  maybe
+    (Left (ValuationSegmentMissing segmentId))
+    Right
+    (segments V.!? index)
+
+canonicalSegmentEndpoints :: ExactSegment -> (ExactPoint, ExactPoint)
+canonicalSegmentEndpoints = uncurry orderedPair . exactSegmentEndpoints
+
+loopPoints :: ExactLoop -> NonEmpty ExactPoint
+loopPoints (ExactLoop points) = points
+
+oneHalf :: ExactRational
+oneHalf = exactRationalFromDyadic 1 (-1)
diff --git a/src-embedding/Moonlight/Triangulation/Internal/Overlay/Embedding.hs b/src-embedding/Moonlight/Triangulation/Internal/Overlay/Embedding.hs
new file mode 100644
--- /dev/null
+++ b/src-embedding/Moonlight/Triangulation/Internal/Overlay/Embedding.hs
@@ -0,0 +1,501 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | Bounded certification of local binary64 embedding obligations for a
+-- declared exact arrangement draft.
+module Moonlight.Triangulation.Internal.Overlay.Embedding
+  ( DraftVertexId (..)
+  , DraftSegmentId (..)
+  , DraftSourceId (..)
+  , DraftIncidence (..)
+  , DraftNeighborhood (..)
+  , ExactArrangementDraft (..)
+  , DraftReference (..)
+  , OverlayEmbeddingObstruction (..)
+  , EmbeddingObligation (..)
+  , EmbeddingResidual
+  , residualUndischargedObligations
+  , milestoneOneResidual
+  , LocalEmbeddingCertificate (..)
+  , certifyLocalEmbedding
+  ) where
+
+import Control.DeepSeq (NFData)
+import Data.List (sort, tails)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+import GHC.Generics (Generic)
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , SegmentRelation
+  , exactOrient2d
+  , exactPointToEmbeddingCandidate
+  , exactSegmentRelation
+  )
+import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)
+import Moonlight.Triangulation.Internal.ExactRational (ExactRational)
+import qualified Moonlight.Triangulation.Math as Math
+  ( orient2d
+  , segmentRelation
+  )
+import Moonlight.Triangulation.Types
+  ( Point (..)
+  , PointValidationError
+  )
+
+-- | Draft-local vertex label.
+newtype DraftVertexId = DraftVertexId Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Draft-local atomic-segment label.
+newtype DraftSegmentId = DraftSegmentId Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Draft-local source-segment label.
+newtype DraftSourceId = DraftSourceId Int
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A declared relation between two atomic draft segments.
+data DraftIncidence = DraftIncidence
+  { -- | First declared atomic segment.
+    draftIncidenceFirstSegment :: !DraftSegmentId
+  , -- | Second declared atomic segment.
+    draftIncidenceSecondSegment :: !DraftSegmentId
+  , -- | Relation declared to hold in both exact and rounded geometry.
+    draftIncidenceRelation :: !SegmentRelation
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | One exact vertex and the cyclic neighbor order declared around it.
+data DraftNeighborhood = DraftNeighborhood
+  { -- | Center of the declared local rotation.
+    draftNeighborhoodCenter :: !DraftVertexId
+  , -- | Neighbors in cyclic rotation order.
+    draftNeighborhoodNeighbors :: !(NonEmpty DraftVertexId)
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A hand-built exact arrangement draft. It contains only declared local
+-- structure; no arrangement or global crossing search is derived here.
+data ExactArrangementDraft = ExactArrangementDraft
+  { -- | Exact coordinates keyed by draft-local vertex identity.
+    draftVertices :: !(Map DraftVertexId ExactPoint)
+  , -- | Atomic segment endpoint identities.
+    draftSegments :: !(Map DraftSegmentId (DraftVertexId, DraftVertexId))
+  , -- | Exact split parameters and vertices in source-segment order.
+    draftSourceMemberships :: !(Map DraftSourceId [(ExactRational, DraftVertexId)])
+  , -- | Segment relations declared to remain invariant under projection.
+    draftIncidences :: ![DraftIncidence]
+  , -- | Cyclic local rotations declared to remain orientation-stable.
+    draftNeighborhoods :: ![DraftNeighborhood]
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | The typed identity of a missing draft reference.
+data DraftReference
+  = DraftVertexReference !DraftVertexId
+  | DraftSegmentReference !DraftSegmentId
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A witness that prevents local embedding certification.
+data OverlayEmbeddingObstruction
+  = DraftReferenceMissing !DraftReference
+  | VertexProjectionRefused !DraftVertexId !PointValidationError
+  | RoundedVerticesCollide !DraftVertexId !DraftVertexId !Point
+  | SplitOrderNotPreserved !DraftSourceId !DraftVertexId !DraftVertexId
+  | IncidenceRelationChanged
+      !DraftSegmentId
+      !DraftSegmentId
+      !SegmentRelation
+      !SegmentRelation
+      !SegmentRelation
+  | NeighborhoodOrientationChanged
+      !DraftVertexId
+      !DraftVertexId
+      !DraftVertexId
+      !Ordering
+      !Ordering
+  | GlobalRelationAdded
+      !DraftSegmentId
+      !DraftSegmentId
+      !SegmentRelation
+  | GlobalRelationRemoved
+      !DraftSegmentId
+      !DraftSegmentId
+      !SegmentRelation
+  | GlobalRelationChanged
+      !DraftSegmentId
+      !DraftSegmentId
+      !SegmentRelation
+      !SegmentRelation
+  | ProjectedSegmentCollapsed
+      !DraftSegmentId
+      !DraftVertexId
+      !DraftVertexId
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | An embedding obligation not discharged by the bounded local certifier.
+data EmbeddingObligation
+  = GlobalNoNewCrossing
+  deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | A nonempty collection of obligations deferred to a later owner.
+newtype EmbeddingResidual = EmbeddingResidual (NonEmpty EmbeddingObligation)
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Read the obligations that remain explicitly undischarged.
+residualUndischargedObligations
+  :: EmbeddingResidual
+  -> NonEmpty EmbeddingObligation
+residualUndischargedObligations (EmbeddingResidual obligations) = obligations
+
+-- | The Milestone 1 residual: the arrangement sweep has not yet proved global
+-- absence of new crossings.
+milestoneOneResidual :: EmbeddingResidual
+milestoneOneResidual = EmbeddingResidual (GlobalNoNewCrossing :| [])
+
+-- | A certificate for exactly four local obligations on a declared draft:
+-- vertex distinctness, source split order, declared incidences, and local
+-- neighborhood orientation. Global absence of new crossings is unproved until
+-- the Milestone 2 arrangement sweep supplies the complete obligation set.
+data LocalEmbeddingCertificate = LocalEmbeddingCertificate
+  { -- | Number of rounded vertex-pair distinctness checks discharged.
+    certificateRoundedVertexDistinctnessCount :: !Int
+  , -- | Number of adjacent source split-order checks discharged.
+    certificateSplitOrderPreservationCount :: !Int
+  , -- | Number of declared incidence checks discharged.
+    certificateIncidenceRelationPreservationCount :: !Int
+  , -- | Number of consecutive neighborhood orientation checks discharged.
+    certificateNeighborhoodOrientationStabilityCount :: !Int
+  , -- | The candidate projection certified by the four local obligations.
+    certificateRoundedVertices :: !(Map DraftVertexId Point)
+  , -- | The necessarily nonempty global obligation residual.
+    certificateResidual :: !EmbeddingResidual
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData)
+
+-- | Certify the four bounded local embedding obligations, collecting every
+-- witness within each obligation and preserving obligation order.
+certifyLocalEmbedding
+  :: ExactArrangementDraft
+  -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate
+certifyLocalEmbedding draft =
+  case NonEmpty.nonEmpty (structuralObstructions draft) of
+    Just obstructions -> Left obstructions
+    Nothing ->
+      case projectVertices (draftVertices draft) of
+        Invalid obstructions -> Left obstructions
+        Valid projectedVertices ->
+          certifyProjectedDraft draft projectedVertices
+
+data Validation value
+  = Invalid !(NonEmpty OverlayEmbeddingObstruction)
+  | Valid value
+
+instance Functor Validation where
+  fmap _ (Invalid obstructions) = Invalid obstructions
+  fmap transform (Valid value) = Valid (transform value)
+
+instance Applicative Validation where
+  pure = Valid
+  Invalid left <*> Invalid right = Invalid (left <> right)
+  Invalid obstructions <*> Valid _ = Invalid obstructions
+  Valid _ <*> Invalid obstructions = Invalid obstructions
+  Valid transform <*> Valid value = Valid (transform value)
+
+invalid :: OverlayEmbeddingObstruction -> Validation value
+invalid obstruction = Invalid (obstruction :| [])
+
+data ProjectedVertex = ProjectedVertex
+  { projectedExactPoint :: !ExactPoint
+  , projectedRoundedPoint :: !Point
+  }
+
+data ResolvedSegment = ResolvedSegment
+  { resolvedSegmentId :: !DraftSegmentId
+  , resolvedSegmentFrom :: !ResolvedVertex
+  , resolvedSegmentTo :: !ResolvedVertex
+  }
+
+data ResolvedVertex = ResolvedVertex
+  { resolvedVertexId :: !DraftVertexId
+  , resolvedProjectedVertex :: !ProjectedVertex
+  }
+
+data ResolvedMembership = ResolvedMembership
+  { resolvedMembershipParameter :: !ExactRational
+  , resolvedMembershipVertex :: !ResolvedVertex
+  }
+
+structuralObstructions
+  :: ExactArrangementDraft
+  -> [OverlayEmbeddingObstruction]
+structuralObstructions draft =
+  segmentEndpointObstructions
+    <> sourceMembershipObstructions
+    <> incidenceObstructions
+    <> neighborhoodObstructions
+ where
+  vertices = draftVertices draft
+  segments = draftSegments draft
+  missingVertex vertexId =
+    [ DraftReferenceMissing (DraftVertexReference vertexId)
+    | Map.notMember vertexId vertices
+    ]
+  missingSegment segmentId =
+    [ DraftReferenceMissing (DraftSegmentReference segmentId)
+    | Map.notMember segmentId segments
+    ]
+  segmentEndpointObstructions =
+    concatMap
+      (\(_, (from, to)) -> missingVertex from <> missingVertex to)
+      (Map.toAscList segments)
+  sourceMembershipObstructions =
+    concatMap
+      (concatMap (missingVertex . snd) . snd)
+      (Map.toAscList (draftSourceMemberships draft))
+  incidenceObstructions =
+    concatMap
+      ( \incidence ->
+          missingSegment (draftIncidenceFirstSegment incidence)
+            <> missingSegment (draftIncidenceSecondSegment incidence)
+      )
+      (draftIncidences draft)
+  neighborhoodObstructions =
+    concatMap
+      ( \neighborhood ->
+          missingVertex (draftNeighborhoodCenter neighborhood)
+            <> concatMap missingVertex (draftNeighborhoodNeighbors neighborhood)
+      )
+      (draftNeighborhoods draft)
+
+projectVertices
+  :: Map DraftVertexId ExactPoint
+  -> Validation (Map DraftVertexId ProjectedVertex)
+projectVertices =
+  Map.traverseWithKey
+    ( \vertexId point ->
+        case exactPointToEmbeddingCandidate point of
+          Left projectionError ->
+            invalid (VertexProjectionRefused vertexId projectionError)
+          Right roundedPoint -> Valid (ProjectedVertex point roundedPoint)
+    )
+
+certifyProjectedDraft
+  :: ExactArrangementDraft
+  -> Map DraftVertexId ProjectedVertex
+  -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate
+certifyProjectedDraft draft projectedVertices =
+  case resolvedFailures of
+    Left obstruction -> Left (obstruction :| [])
+    Right localFailures ->
+      case NonEmpty.nonEmpty (collisionFailures <> localFailures) of
+        Just obstructions -> Left obstructions
+        Nothing ->
+          Right
+            LocalEmbeddingCertificate
+              { certificateRoundedVertexDistinctnessCount = distinctnessCount
+              , certificateSplitOrderPreservationCount = splitOrderCount
+              , certificateIncidenceRelationPreservationCount = incidenceCount
+              , certificateNeighborhoodOrientationStabilityCount = neighborhoodCount
+              , certificateRoundedVertices = Map.map projectedRoundedPoint projectedVertices
+              , certificateResidual = milestoneOneResidual
+              }
+ where
+  collisionFailures = roundedVertexCollisionObstructions projectedVertices
+  resolvedFailures = do
+    splitFailures <- traverse (uncurry resolveSource) (Map.toAscList (draftSourceMemberships draft))
+    incidenceFailures <- traverse resolveIncidence (draftIncidences draft)
+    neighborhoodFailures <- traverse resolveNeighborhood (draftNeighborhoods draft)
+    pure (concat splitFailures <> concat incidenceFailures <> concat neighborhoodFailures)
+  resolveVertex vertexId =
+    case Map.lookup vertexId projectedVertices of
+      Nothing -> Left (DraftReferenceMissing (DraftVertexReference vertexId))
+      Just projectedVertex -> Right (ResolvedVertex vertexId projectedVertex)
+  resolveSegment segmentId =
+    case Map.lookup segmentId (draftSegments draft) of
+      Nothing -> Left (DraftReferenceMissing (DraftSegmentReference segmentId))
+      Just (from, to) ->
+        ResolvedSegment segmentId
+          <$> resolveVertex from
+          <*> resolveVertex to
+  resolveSource sourceId memberships =
+    splitOrderObstructions sourceId
+      <$> traverse
+        ( \(parameter, vertexId) ->
+            ResolvedMembership parameter <$> resolveVertex vertexId
+        )
+        memberships
+  resolveIncidence incidence =
+    incidenceRelationObstructions
+      (draftIncidenceRelation incidence)
+      <$> resolveSegment (draftIncidenceFirstSegment incidence)
+      <*> resolveSegment (draftIncidenceSecondSegment incidence)
+  resolveNeighborhood neighborhood =
+    neighborhoodOrientationObstructions
+      <$> resolveVertex (draftNeighborhoodCenter neighborhood)
+      <*> traverse resolveVertex (draftNeighborhoodNeighbors neighborhood)
+  vertexCount = Map.size projectedVertices
+  distinctnessCount = vertexCount * (vertexCount - 1) `quot` 2
+  splitOrderCount =
+    sum
+      ( map
+          (max 0 . subtract 1 . length)
+          (Map.elems (draftSourceMemberships draft))
+      )
+  incidenceCount = length (draftIncidences draft)
+  neighborhoodCount =
+    sum
+      ( map
+          (length . draftNeighborhoodNeighbors)
+          (draftNeighborhoods draft)
+      )
+
+roundedVertexCollisionObstructions
+  :: Map DraftVertexId ProjectedVertex
+  -> [OverlayEmbeddingObstruction]
+roundedVertexCollisionObstructions projectedVertices =
+  [ RoundedVerticesCollide leftId rightId roundedPoint
+  | (roundedPoint, vertexIds) <- Map.toAscList verticesByRoundedPoint
+  , (leftId : remainingIds) <- tails (sort vertexIds)
+  , rightId <- remainingIds
+  ]
+ where
+  verticesByRoundedPoint =
+    Map.fromListWith (<>)
+      [ (projectedRoundedPoint projectedVertex, [vertexId])
+      | (vertexId, projectedVertex) <- Map.toAscList projectedVertices
+      ]
+
+splitOrderObstructions
+  :: DraftSourceId
+  -> [ResolvedMembership]
+  -> [OverlayEmbeddingObstruction]
+splitOrderObstructions sourceId memberships =
+  case memberships of
+    firstMembership : secondMembership : remainingMemberships ->
+      let finalMembership =
+            foldl' (\_ current -> current) secondMembership remainingMemberships
+          sourceFrom = roundedMembershipPoint firstMembership
+          sourceTo = roundedMembershipPoint finalMembership
+       in [ SplitOrderNotPreserved
+              sourceId
+              (resolvedVertexId (resolvedMembershipVertex leftMembership))
+              (resolvedVertexId (resolvedMembershipVertex rightMembership))
+          | (leftMembership, rightMembership) <-
+              zip
+                memberships
+                (drop 1 memberships)
+          , compare
+              (resolvedMembershipParameter leftMembership)
+              (resolvedMembershipParameter rightMembership)
+              /= roundedOrderAlong
+                sourceFrom
+                sourceTo
+                (roundedMembershipPoint leftMembership)
+                (roundedMembershipPoint rightMembership)
+          ]
+    _ -> []
+
+roundedMembershipPoint :: ResolvedMembership -> Point
+roundedMembershipPoint =
+  projectedRoundedPoint
+    . resolvedProjectedVertex
+    . resolvedMembershipVertex
+
+roundedOrderAlong :: Point -> Point -> Point -> Point -> Ordering
+roundedOrderAlong
+  (Point sourceFromX sourceFromY)
+  (Point sourceToX sourceToY)
+  (Point leftX leftY)
+  (Point rightX rightY) =
+    let directionX = sourceToX - sourceFromX
+        directionY = sourceToY - sourceFromY
+     in if abs directionX >= abs directionY
+          then
+            if directionX >= 0
+              then compare leftX rightX
+              else compare rightX leftX
+          else
+            if directionY >= 0
+              then compare leftY rightY
+              else compare rightY leftY
+
+incidenceRelationObstructions
+  :: SegmentRelation
+  -> ResolvedSegment
+  -> ResolvedSegment
+  -> [OverlayEmbeddingObstruction]
+incidenceRelationObstructions declaredRelation firstSegment secondSegment =
+  [ IncidenceRelationChanged
+      (resolvedSegmentId firstSegment)
+      (resolvedSegmentId secondSegment)
+      declaredRelation
+      exactRelation
+      roundedRelation
+ | exactRelation /= declaredRelation || roundedRelation /= declaredRelation
+  ]
+ where
+  exactRelation =
+    relationFor exactSegmentRelation projectedExactPoint firstSegment secondSegment
+  roundedRelation =
+    relationFor Math.segmentRelation projectedRoundedPoint firstSegment secondSegment
+
+relationFor
+  :: (point -> point -> point -> point -> SegmentRelation)
+  -> (ProjectedVertex -> point)
+  -> ResolvedSegment
+  -> ResolvedSegment
+  -> SegmentRelation
+relationFor relation project firstSegment secondSegment =
+  relation
+    (project (resolvedProjectedVertex (resolvedSegmentFrom firstSegment)))
+    (project (resolvedProjectedVertex (resolvedSegmentTo firstSegment)))
+    (project (resolvedProjectedVertex (resolvedSegmentFrom secondSegment)))
+    (project (resolvedProjectedVertex (resolvedSegmentTo secondSegment)))
+
+neighborhoodOrientationObstructions
+  :: ResolvedVertex
+  -> NonEmpty ResolvedVertex
+  -> [OverlayEmbeddingObstruction]
+neighborhoodOrientationObstructions center neighbors =
+  [ NeighborhoodOrientationChanged
+      (resolvedVertexId center)
+      (resolvedVertexId leftNeighbor)
+      (resolvedVertexId rightNeighbor)
+      exactOrientation
+      roundedOrientation
+  | (leftNeighbor, rightNeighbor) <-
+      cyclePairs neighbors
+  , let exactOrientation =
+          exactOrient2d
+            (exactVertexPoint center)
+            (exactVertexPoint leftNeighbor)
+            (exactVertexPoint rightNeighbor)
+        roundedOrientation =
+          Math.orient2d
+            (roundedVertexPoint center)
+            (roundedVertexPoint leftNeighbor)
+            (roundedVertexPoint rightNeighbor)
+  , exactOrientation /= roundedOrientation
+  ]
+
+exactVertexPoint :: ResolvedVertex -> ExactPoint
+exactVertexPoint = projectedExactPoint . resolvedProjectedVertex
+
+roundedVertexPoint :: ResolvedVertex -> Point
+roundedVertexPoint = projectedRoundedPoint . resolvedProjectedVertex
diff --git a/src-ffi/Moonlight/Triangulation/Foreign/ABI.hs b/src-ffi/Moonlight/Triangulation/Foreign/ABI.hs
--- a/src-ffi/Moonlight/Triangulation/Foreign/ABI.hs
+++ b/src-ffi/Moonlight/Triangulation/Foreign/ABI.hs
@@ -399,6 +399,7 @@
     T.FaceCapacityExceeded requested capacity -> indices 52 requested capacity
     T.PayloadStorageFailure _ -> codeOnly 53
     T.CoordinatePayloadCountMismatch coordinates payloads -> indices 54 coordinates payloads
+    T.CircleSweepRequiresDenseStorage -> codeOnly 55
   )
     {obstructionMessage = show failure}
  where
diff --git a/src-public/Moonlight/Triangulation.hs b/src-public/Moonlight/Triangulation.hs
--- a/src-public/Moonlight/Triangulation.hs
+++ b/src-public/Moonlight/Triangulation.hs
@@ -125,7 +125,7 @@
   , constraintSegments
   , unionConstrainedWith
   , unionConstrained
-  , joinSeparatedConstrainedWith
+  , joinSeparatedConstrained
   , extendConstrainedWith
 
     -- * Refinement — budget-bounded, composed after any operation above rather
@@ -166,6 +166,7 @@
   , outerFace
   , faceDirectedEdges
   , faceVertices
+  , innerFaceDirectedEdgeTriples
   , innerFaceVertexTriples
   , vertexOutgoingEdges
   , numUndirectedEdges
@@ -182,6 +183,130 @@
   , LocationHint (..)
   , facesAtEvenBarrierDepth
 
+    -- ** Face regions and alpha filtration
+  , FaceComponent
+  , faceComponentFaces
+  , BoundaryLoop
+  , boundaryLoopVertices
+  , RegionBoundary
+  , regionBoundaryOuterLoop
+  , regionBoundaryHoleLoops
+  , BoundaryObstruction (..)
+  , faceComponents
+  , componentBoundary
+  , RadiusSquared
+  , RadiusSquaredError (..)
+  , mkRadiusSquared
+  , alphaShapeContainsFace
+
+    -- * Exact planar regions — authoritative rational geometry, labelled
+    -- overlay, closed cell selections, valuations, and polygonal morphology
+  , ExactRational
+  , ExactArithmeticError (..)
+  , exactRational
+  , exactRationalNumerator
+  , exactRationalDenominator
+  , ExactPoint
+  , exactPoint
+  , exactPointCoordinates
+  , ExactSegment
+  , ExactGeometryError (..)
+  , exactSegment
+  , exactSegmentEndpoints
+  , exactPointFromPoint
+  , exactPointFromQueryPoint
+  , exactPointToEmbeddingCandidate
+  , exactOrient2d
+  , exactOnClosedSegment
+  , SegmentRelation (..)
+  , exactSegmentRelation
+  , ExactIntersectionError (..)
+  , exactLineIntersection
+  , ExactLoop
+  , exactLoop
+  , exactLoopPoints
+  , PolygonComponent
+  , polygonComponent
+  , polygonOuterLoop
+  , polygonHoleLoops
+  , PlanarRegion
+  , planarRegion
+  , planarRegionComponents
+  , emptyPlanarRegion
+  , RegionPointLocation (..)
+  , regionPointLocation
+  , PlanarLayer
+  , planarLayerOutsideLabel
+  , planarLayerRegions
+  , planarLayer
+  , planarLayerLabelAt
+  , RegionValidationError (..)
+  , RegionPublicationError (..)
+  , labelledPlanarLayer
+  , ExactCellSet
+  , CellSelectionError (..)
+  , exactCellSet
+  , closeFaceCellSet
+  , exactCellSetVertexCount
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , foldExactCellVertices
+  , foldExactCellEdges
+  , foldExactCellFaces
+  , OverlayResult
+  , OverlayReceipt (..)
+  , OverlayError (..)
+  , OverlaySelectionKind (..)
+  , OverlaySelectionError (..)
+  , overlayLayers
+  , overlayEmbeddedTriangulation
+  , overlayReceipt
+  , overlayCells
+  , overlayArrangementVertices
+  , overlayArrangementEdges
+  , overlayPlanarLayer
+  , overlaySelectedRegion
+  , overlayClosedUnion
+  , overlayClosedIntersection
+  , overlayRegularizedDifference
+  , EulerCharacteristic
+  , eulerCharacteristicValue
+  , ExactArea
+  , exactAreaValue
+  , ExactLengthTerm
+  , lengthCoefficient
+  , squaredLength
+  , ExactLengthExpression
+  , exactLengthTerms
+  , CertifiedInterval (..)
+  , ExactLengthMeasurement
+  , exactLengthExpression
+  , exactLengthBounds
+  , PlanarValuations
+  , valuationEuler
+  , valuationArea
+  , valuationIntrinsic1
+  , ValuationError (..)
+  , cellValuations
+  , regionValuations
+  , cellSetPerimeter
+  , regionPerimeter
+  , ConvexPolygon
+  , convexPolygon
+  , convexPolygonPoints
+  , StructuringElement
+  , structuringElement
+  , MinkowskiOperation (..)
+  , MinkowskiError (..)
+  , MinkowskiReceipt (..)
+  , convexMinkowskiSum
+  , minkowskiSum
+  , erodeBy
+  , openWith
+  , closeWith
+  , polygonOffset
+  , polygonInset
+
     -- * Discharge — the invariants the constructors guarantee, checkable on a
     -- value built by any route; every violation is a value carrying its witness
   , validateTriangulation
@@ -194,10 +319,23 @@
   , delaunayFromCoordinates
   , delaunayGeometry
   )
+import Moonlight.Triangulation.CellSet
+  ( CellSelectionError (..)
+  , ExactCellSet
+  , closeFaceCellSet
+  , exactCellSet
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , exactCellSetVertexCount
+  , foldExactCellEdges
+  , foldExactCellFaces
+  , foldExactCellVertices
+  )
 import Moonlight.Triangulation.Dcel
   ( destination
   , faceDirectedEdges
   , faceVertices
+  , innerFaceDirectedEdgeTriples
   , innerFaceVertexTriples
   , incidentFace
   , isBoundaryEdge
@@ -214,7 +352,41 @@
   , vertexPoint
   , vertexPoints
   )
-import Moonlight.Triangulation.FloodFillIterator (facesAtEvenBarrierDepth)
+import Moonlight.Triangulation.FloodFillIterator
+  ( BoundaryLoop
+  , BoundaryObstruction (..)
+  , FaceComponent
+  , RadiusSquared
+  , RegionBoundary
+  , RadiusSquaredError (..)
+  , alphaShapeContainsFace
+  , boundaryLoopVertices
+  , componentBoundary
+  , faceComponentFaces
+  , faceComponents
+  , facesAtEvenBarrierDepth
+  , mkRadiusSquared
+  , regionBoundaryHoleLoops
+  , regionBoundaryOuterLoop
+  )
+import Moonlight.Triangulation.Exact
+  ( ExactGeometryError (..)
+  , ExactIntersectionError (..)
+  , ExactPoint
+  , ExactSegment
+  , SegmentRelation (..)
+  , exactLineIntersection
+  , exactOnClosedSegment
+  , exactOrient2d
+  , exactPoint
+  , exactPointCoordinates
+  , exactPointFromPoint
+  , exactPointFromQueryPoint
+  , exactPointToEmbeddingCandidate
+  , exactSegment
+  , exactSegmentEndpoints
+  , exactSegmentRelation
+  )
 import Moonlight.Triangulation.Handles.HandleDefs
   ( DirectedEdgeId (..)
   , FaceId (..)
@@ -276,10 +448,17 @@
 import Moonlight.Triangulation.Internal.Cdt.Union
   ( constraintSegments
   , extendConstrainedWith
-  , joinSeparatedConstrainedWith
+  , joinSeparatedConstrained
   , unionConstrained
   , unionConstrainedWith
   )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactArithmeticError (..)
+  , ExactRational
+  , exactRational
+  , exactRationalDenominator
+  , exactRationalNumerator
+  )
 import Moonlight.Triangulation.Internal.Representation
   ( BuildResult
   , ConstrainedDelaunayTriangulation
@@ -328,7 +507,42 @@
   , interpolateNearest
   , nearestNeighbor
   )
+import Moonlight.Triangulation.Minkowski
+  ( ConvexPolygon
+  , MinkowskiError (..)
+  , MinkowskiOperation (..)
+  , MinkowskiReceipt (..)
+  , StructuringElement
+  , closeWith
+  , convexMinkowskiSum
+  , convexPolygon
+  , convexPolygonPoints
+  , erodeBy
+  , minkowskiSum
+  , openWith
+  , polygonInset
+  , polygonOffset
+  , structuringElement
+  )
 import Moonlight.Triangulation.Math (mkQueryPoint)
+import Moonlight.Triangulation.Overlay
+  ( OverlayError (..)
+  , OverlayReceipt (..)
+  , OverlayResult
+  , OverlaySelectionError (..)
+  , OverlaySelectionKind (..)
+  , overlayArrangementEdges
+  , overlayArrangementVertices
+  , overlayCells
+  , overlayClosedIntersection
+  , overlayClosedUnion
+  , overlayEmbeddedTriangulation
+  , overlayLayers
+  , overlayPlanarLayer
+  , overlayReceipt
+  , overlayRegularizedDifference
+  , overlaySelectedRegion
+  )
 import Moonlight.Triangulation.Payload (mapVertices)
 import Moonlight.Triangulation.JoinSemilattice (JoinSemilattice (..))
 import Moonlight.Triangulation.Refinement
@@ -338,6 +552,29 @@
   , validateRefinementParameters
   , withMinimumAngle
   )
+import Moonlight.Triangulation.Region
+  ( ExactLoop
+  , PlanarLayer
+  , PlanarRegion
+  , PolygonComponent
+  , RegionPointLocation (..)
+  , RegionPublicationError (..)
+  , RegionValidationError (..)
+  , emptyPlanarRegion
+  , exactLoop
+  , exactLoopPoints
+  , labelledPlanarLayer
+  , planarLayer
+  , planarLayerLabelAt
+  , planarLayerOutsideLabel
+  , planarLayerRegions
+  , planarRegion
+  , planarRegionComponents
+  , polygonComponent
+  , polygonHoleLoops
+  , polygonOuterLoop
+  , regionPointLocation
+  )
 import Moonlight.Triangulation.SetAlgebra
   ( difference
   , intersection
@@ -348,3 +585,27 @@
   , unions
   )
 import Moonlight.Triangulation.Validation (validateTriangulation)
+import Moonlight.Triangulation.Valuation
+  ( CertifiedInterval (..)
+  , EulerCharacteristic
+  , ExactArea
+  , ExactLengthExpression
+  , ExactLengthMeasurement
+  , ExactLengthTerm
+  , PlanarValuations
+  , ValuationError (..)
+  , cellSetPerimeter
+  , cellValuations
+  , eulerCharacteristicValue
+  , exactAreaValue
+  , exactLengthBounds
+  , exactLengthExpression
+  , exactLengthTerms
+  , lengthCoefficient
+  , regionPerimeter
+  , regionValuations
+  , squaredLength
+  , valuationArea
+  , valuationEuler
+  , valuationIntrinsic1
+  )
diff --git a/src-serialize/Moonlight/Triangulation/Serialization.hs b/src-serialize/Moonlight/Triangulation/Serialization.hs
--- a/src-serialize/Moonlight/Triangulation/Serialization.hs
+++ b/src-serialize/Moonlight/Triangulation/Serialization.hs
@@ -1,23 +1,29 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
 -- | The versioned binary surface: encode a triangulation to bytes and read it
 -- back. Decoding refuses a payload whose format version or coordinate encoding this
 -- build does not own, rather than reinterpreting it.
 module Moonlight.Triangulation.Serialization
-  ( SerializationError (..)
+  ( DecodingBudget (..)
+  , TrustedPayloadDecoders
+  , trustedBinaryPayloadDecoders
+  , SerializedCountKind (..)
+  , SerializationError (..)
   , serializationVersion
   , encodeTriangulation
   , decodeTriangulation
   ) where
 
-import Control.Monad (replicateM, unless, when)
+import Control.Monad (unless, when)
 import Control.Monad.Trans.Class (lift)
 import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
 import Data.Binary (Binary (..))
 import Data.Binary.Get
   ( Get
+  , bytesRead
   , getDoublebe
   , getWord16be
   , getWord32be
@@ -26,8 +32,7 @@
   , runGetOrFail
   )
 import Data.Binary.Put
-  ( Put
-  , putDoublebe
+  ( putDoublebe
   , putWord16be
   , putWord32be
   , putWord64be
@@ -39,13 +44,18 @@
 import qualified Data.IntSet as IntSet
 import Data.Int (Int64)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map.Strict as Map
 import Data.Proxy (Proxy (..))
+import qualified Data.Set as Set
 import qualified Data.Vector as V
 import qualified Data.Vector.Unboxed as U
 import Data.Word (Word16, Word64, Word8)
-import Moonlight.Triangulation.Internal.BoxedPaged (boxedFromVector, boxedToVector)
+import Moonlight.Triangulation.Internal.BoxedPaged
+  ( boxedFill
+  , boxedFromVector
+  , boxedToVector
+  )
 import Moonlight.Triangulation.Internal.Paged (fromLocalVector, fromVector, toVector)
+import Moonlight.Triangulation.Internal.PackedIndex (indexLimit)
 import Moonlight.Triangulation.Internal.PointIndex (buildPointIndex)
 import Moonlight.Triangulation.Handles.HandleDefs
 import Moonlight.Triangulation.Internal.Representation
@@ -73,6 +83,49 @@
   put (UndirectedEdgeId value) = putWord32be value
   get = UndirectedEdgeId <$> getWord32be
 
+-- | The finite resource envelope admitted by the canonical decoder. The byte
+-- budget bounds the complete input before parsing; the element budget bounds
+-- the total number of library-owned serialized section elements before any
+-- section is allocated.
+data DecodingBudget = DecodingBudget
+  { decodingMaximumInputBytes :: !Word64
+  , decodingMaximumSectionElements :: !Word64
+  }
+  deriving stock (Eq, Show)
+
+-- | Evidence that the caller accepts the internal resource behavior of all
+-- four payload decoders. The structural budget governs only containers owned
+-- by this module; executable payload decoders require this separate trust law.
+data TrustedPayloadDecoders vertex directed undirected face where
+  TrustedBinaryPayloadDecoders
+    :: ( Binary vertex
+       , Binary directed
+       , Binary undirected
+       , Binary face
+       )
+    => TrustedPayloadDecoders vertex directed undirected face
+
+-- | Explicitly trust the selected 'Binary' payload instances. This witness is
+-- required because arbitrary instances may allocate independently of input
+-- bytes; constructing it declares that the caller has audited that behavior.
+trustedBinaryPayloadDecoders
+  :: ( Binary vertex
+     , Binary directed
+     , Binary undirected
+     , Binary face
+     )
+  => TrustedPayloadDecoders vertex directed undirected face
+trustedBinaryPayloadDecoders = TrustedBinaryPayloadDecoders
+
+-- | The structural count whose encoded value was outside the resident index
+-- or allocation domain.
+data SerializedCountKind
+  = SerializedVertexCount
+  | SerializedDirectedEdgeCount
+  | SerializedFaceCount
+  | SerializedConstraintCount
+  deriving stock (Eq, Ord, Show)
+
 -- | Every way serialization refuses, each naming its witness.
 data SerializationError
   = BinaryDecodeFailure !Int64 !String
@@ -81,19 +134,15 @@
   | UnsupportedFormatVersion !Word16
   | ConstraintModeTagMismatch !Word8 !Word8
   | CoordinateEncodingTagMismatch !Word8 !Word8
-  | EncodedCountExceedsInt !Word64
-  | SerializedCoordinateLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedVertexPayloadLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedVertexOutgoingLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedDirectedEdgeCountOdd {-# UNPACK #-} !Int
-  | SerializedNextLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedPreviousLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedFaceReferenceLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedDirectedPayloadLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedUndirectedPayloadLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
-  | SerializedConstraintLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
+  | InputByteBudgetExceeded !Word64 !Word64
+  | DecodedSectionBudgetExceeded !Word64 !Word64
+  | EncodedCountExceedsInt !SerializedCountKind !Word64
+  | EncodedCountExceedsPackedIndex !SerializedCountKind !Word64 !Word64
+  | SerializedDirectedEdgeCountOdd !Word64
+  | SerializedConstraintCountExceedsEdges !Word64 !Word64
+  | SerializedPlanarCardinalityMismatch !Word64 !Word64 !Word64
+  | SerializedFixedBodyTooShort !Word64 !Word64
   | SerializedMissingOuterFace
-  | SerializedFacePayloadLengthMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
   | InvalidSerializedPoint {-# UNPACK #-} !Int !PointValidationError
   | NonCanonicalSerializedConstraintFlag !UndirectedEdgeId !Word8
   | SerializedConstraintCountMismatch {-# UNPACK #-} !Int {-# UNPACK #-} !Int
@@ -106,7 +155,7 @@
 
 -- | The envelope version this module writes.
 serializationVersion :: Word16
-serializationVersion = 4
+serializationVersion = 6
 
 formatMagic :: Word64
 formatMagic = 0x5350414445485307 -- "SPADEHS" + canonical geometry-owned format family
@@ -125,55 +174,87 @@
   putWord8 (modeTag (constraintModeValue (modeProxy triangulation)))
   putWord8 binary64EncodingTag
   let ElementDefaults directedDefault undirectedDefault faceDefault = triElementDefaults triangulation
+      pointXs = toVector (triPointX triangulation)
+      pointYs = toVector (triPointY triangulation)
+      vertexDefault = boxedFill (triVertexData triangulation)
+      vertexDataVector = boxedToVector (triVertexData triangulation)
+      vertexOut = toVector (triVertexOut triangulation)
+      topology = toVector (triHalfTopology triangulation)
+      directedDataVector = boxedToVector (triDirectedData triangulation)
+      undirectedDataVector = boxedToVector (triUndirectedData triangulation)
+      faceEdge = toVector (triFaceEdge triangulation)
+      faceDataVector = boxedToVector (triFaceData triangulation)
+      constraints = toVector (triConstraint triangulation)
+      vertexCount = U.length pointXs
+      directedEdgeCount = U.length topology `quot` 4
+      faceCount = U.length faceEdge
+  -- Version 6 commits every structural count in one prefix. The decoder can
+  -- prove their relationships and resource bounds before defaults, payloads,
+  -- or section bodies are evaluated.
+  putWord64be (fromIntegral vertexCount)
+  putWord64be (fromIntegral directedEdgeCount)
+  putWord64be (fromIntegral faceCount)
+  putWord64be (fromIntegral (triConstraintCount triangulation))
   put directedDefault
   put undirectedDefault
   put faceDefault
   -- Geometry and payloads are independent components. Persist the authoritative
   -- coordinate pages rather than attempting to recover them from annotations.
-  putUVector putDoublebe (toVector (triPointX triangulation))
-  putUVector putDoublebe (toVector (triPointY triangulation))
-  putBoxedVector put (boxedToVector (triVertexData triangulation))
-  putUVector putWord32be (toVector (triVertexOut triangulation))
+  U.mapM_ putDoublebe pointXs
+  U.mapM_ putDoublebe pointYs
+  put vertexDefault
+  V.mapM_ put vertexDataVector
+  U.mapM_ putWord32be vertexOut
   -- The wire format stores the four topology planes separately; the interleaved
   -- arena is a resident layout, not a serialization concern.
-  let topology = toVector (triHalfTopology triangulation)
-      plane field = U.generate (U.length topology `quot` 4) (\edge -> topology U.! (4 * edge + field))
-  putUVector putWord32be (plane 0)
-  putUVector putWord32be (plane 1)
-  putUVector putWord32be (plane 2)
-  putUVector putWord32be (plane 3)
-  putBoxedVector put (boxedToVector (triDirectedData triangulation))
-  putBoxedVector put (boxedToVector (triUndirectedData triangulation))
-  putUVector putWord32be (toVector (triFaceEdge triangulation))
-  putBoxedVector put (boxedToVector (triFaceData triangulation))
-  putUVector putWord8 (toVector (triConstraint triangulation))
-  putCount (fromIntegral (triConstraintCount triangulation))
+  let plane field = U.generate directedEdgeCount (\edge -> topology U.! (4 * edge + field))
+  U.mapM_ putWord32be (plane 0)
+  U.mapM_ putWord32be (plane 1)
+  U.mapM_ putWord32be (plane 2)
+  U.mapM_ putWord32be (plane 3)
+  V.mapM_ put directedDataVector
+  V.mapM_ put undirectedDataVector
+  U.mapM_ putWord32be faceEdge
+  V.mapM_ put faceDataVector
+  U.mapM_ putWord8 constraints
 
--- | Decode one exact, versioned finite DCEL. Coordinate uniqueness and the
--- complete topology, geometry, and Delaunay/CDT invariants are checked before
--- the opaque value is returned.
+-- | Decode one exact, versioned finite DCEL inside an explicit resource
+-- envelope. Structural counts are read and reconciled before any default,
+-- payload, or section body is decoded. Coordinate uniqueness and the complete
+-- topology, geometry, and Delaunay/CDT invariants are checked before the opaque
+-- value is returned.
+--
+-- The budget bounds the input and the containers owned by this module. The
+-- required 'TrustedPayloadDecoders' witness separately records the caller's
+-- decision that every selected payload decoder is internally resource-safe.
 decodeTriangulation
   :: forall mode vertex directed undirected face.
-     ( KnownConstraintMode mode
-     , Binary vertex
-     , Binary directed
-     , Binary undirected
-     , Binary face
-     )
-  => BL.ByteString
+     KnownConstraintMode mode
+  => DecodingBudget
+  -> TrustedPayloadDecoders vertex directed undirected face
+  -> BL.ByteString
   -> Either SerializationError (Triangulation mode vertex directed undirected face)
-decodeTriangulation bytes =
-  case runGetOrFail (runExceptT getTriangulation) bytes of
-    Left (_, offset, message) -> Left (BinaryDecodeFailure offset message)
-    Right (_, _, Left failure) -> Left failure
-    Right (remaining, _, Right triangulation)
-      | not (BL.null remaining) -> Left (TrailingBytes (BL.length remaining))
-      | otherwise ->
-          case validateTriangulation triangulation of
-            [] -> Right triangulation
-            firstViolation : remainingViolations ->
-              Left (DecodedInvariantViolations (firstViolation :| remainingViolations))
+decodeTriangulation budget TrustedBinaryPayloadDecoders bytes
+  | inputByteCount > decodingMaximumInputBytes budget =
+      Left
+        ( InputByteBudgetExceeded
+            inputByteCount
+            (decodingMaximumInputBytes budget)
+        )
+  | otherwise =
+      case runGetOrFail (runExceptT getTriangulation) bytes of
+        Left (_, offset, message) -> Left (BinaryDecodeFailure offset message)
+        Right (_, _, Left failure) -> Left failure
+        Right (trailing, _, Right triangulation)
+          | not (BL.null trailing) -> Left (TrailingBytes (BL.length trailing))
+          | otherwise ->
+              case validateTriangulation triangulation of
+                [] -> Right triangulation
+                firstViolation : remainingViolations ->
+                  Left (DecodedInvariantViolations (firstViolation :| remainingViolations))
  where
+  inputByteCount = fromIntegral (BL.length bytes)
+
   getTriangulation :: Decoder (Triangulation mode vertex directed undirected face)
   getTriangulation = do
     magic <- lift getWord64be
@@ -186,51 +267,50 @@
     encodedScalar <- lift getWord8
     let expectedScalar = binary64EncodingTag
     unless (encodedScalar == expectedScalar) (throwE (CoordinateEncodingTagMismatch expectedScalar encodedScalar))
-    defaults <- ElementDefaults <$> lift get <*> lift get <*> lift get
-    pointXs <- getUVector (lift getDoublebe)
-    pointYs <- getUVector (lift getDoublebe)
-    vertexDataVector <- getBoxedVector (lift get)
-    vertexOut <- getUVector (lift getWord32be)
-    halfOrigin <- getUVector (lift getWord32be)
-    halfNext <- getUVector (lift getWord32be)
-    halfPrev <- getUVector (lift getWord32be)
-    halfFace <- getUVector (lift getWord32be)
-    directedDataVector <- getBoxedVector (lift get)
-    undirectedDataVector <- getBoxedVector (lift get)
-    faceEdge <- getUVector (lift getWord32be)
-    faceDataVector <- getBoxedVector (lift get)
-    constraints <- getUVector (lift getWord8)
-    cachedConstraintCount <- getCount
+    encodedVertexCount <- lift getWord64be
+    encodedDirectedEdgeCount <- lift getWord64be
+    encodedFaceCount <- lift getWord64be
+    encodedConstraintCount <- lift getWord64be
+    validateStructuralPrefix
+      budget
+      encodedVertexCount
+      encodedDirectedEdgeCount
+      encodedFaceCount
+      encodedConstraintCount
+    prefixByteCount <- fromIntegral <$> lift bytesRead
+    let bodyByteCount = inputByteCount - prefixByteCount
+        minimumBodyByteCount =
+          minimumFixedBodyBytes
+            encodedVertexCount
+            encodedDirectedEdgeCount
+            encodedFaceCount
+    unless (bodyByteCount >= minimumBodyByteCount) $
+      throwE (SerializedFixedBodyTooShort bodyByteCount minimumBodyByteCount)
 
-    let vertexCount = U.length pointXs
-        pointYCount = U.length pointYs
-        vertexPayloadCount = V.length vertexDataVector
-        vertexOutgoingCount = U.length vertexOut
-        halfCount = U.length halfOrigin
-        nextCount = U.length halfNext
-        previousCount = U.length halfPrev
-        faceReferenceCount = U.length halfFace
+    let vertexCount = fromIntegral encodedVertexCount
+        halfCount = fromIntegral encodedDirectedEdgeCount
         edgeCount = halfCount `quot` 2
-        faceCount = U.length faceEdge
-        directedPayloadCount = V.length directedDataVector
-        undirectedPayloadCount = V.length undirectedDataVector
-        constraintCount = U.length constraints
-        facePayloadCount = V.length faceDataVector
-        points = zipWith Point (U.toList pointXs) (U.toList pointYs)
-    unless (pointYCount == vertexCount) (throwE (SerializedCoordinateLengthMismatch vertexCount pointYCount))
-    unless (vertexPayloadCount == vertexCount) (throwE (SerializedVertexPayloadLengthMismatch vertexPayloadCount vertexCount))
-    unless (vertexOutgoingCount == vertexCount) (throwE (SerializedVertexOutgoingLengthMismatch vertexOutgoingCount vertexCount))
-    unless (even halfCount) (throwE (SerializedDirectedEdgeCountOdd halfCount))
-    unless (nextCount == halfCount) (throwE (SerializedNextLengthMismatch nextCount halfCount))
-    unless (previousCount == halfCount) (throwE (SerializedPreviousLengthMismatch previousCount halfCount))
-    unless (faceReferenceCount == halfCount) (throwE (SerializedFaceReferenceLengthMismatch faceReferenceCount halfCount))
-    unless (directedPayloadCount == halfCount) (throwE (SerializedDirectedPayloadLengthMismatch directedPayloadCount halfCount))
-    unless (undirectedPayloadCount == edgeCount) (throwE (SerializedUndirectedPayloadLengthMismatch undirectedPayloadCount edgeCount))
-    unless (constraintCount == edgeCount) (throwE (SerializedConstraintLengthMismatch constraintCount edgeCount))
-    unless (faceCount >= 1) (throwE SerializedMissingOuterFace)
-    unless (facePayloadCount == faceCount) (throwE (SerializedFacePayloadLengthMismatch facePayloadCount faceCount))
-    traverse_ (uncurry validateStoredPoint) (zip [0 ..] points)
+        faceCount = fromIntegral encodedFaceCount
+        cachedConstraintCount = fromIntegral encodedConstraintCount
+    defaults <- ElementDefaults <$> lift get <*> lift get <*> lift get
+    pointXs <- U.replicateM vertexCount (lift getDoublebe)
+    pointYs <- U.replicateM vertexCount (lift getDoublebe)
+    vertexDefault <- lift get
+    vertexDataVector <- V.replicateM vertexCount (lift get)
+    vertexOut <- U.replicateM vertexCount (lift getWord32be)
+    halfOrigin <- U.replicateM halfCount (lift getWord32be)
+    halfNext <- U.replicateM halfCount (lift getWord32be)
+    halfPrev <- U.replicateM halfCount (lift getWord32be)
+    halfFace <- U.replicateM halfCount (lift getWord32be)
+    directedDataVector <- V.replicateM halfCount (lift get)
+    undirectedDataVector <- V.replicateM edgeCount (lift get)
+    faceEdge <- U.replicateM faceCount (lift getWord32be)
+    faceDataVector <- V.replicateM faceCount (lift get)
+    constraints <- U.replicateM edgeCount (lift getWord8)
 
+    let points = V.generate vertexCount (\index -> Point (pointXs U.! index) (pointYs U.! index))
+    traverse_ (uncurry validateStoredPoint) (V.indexed points)
+
     case U.ifoldr (\index flag found -> if flag /= 0 && flag /= 1 then Just (index, flag) else found) Nothing constraints of
       Nothing -> pure ()
       Just (index, flag) ->
@@ -243,8 +323,7 @@
     unless (cachedConstraintCount == actualConstraintCount) (throwE (SerializedConstraintCountMismatch cachedConstraintCount actualConstraintCount))
     when (expectedMode == 0 && actualConstraintCount /= 0) (throwE (UnconstrainedSerializedConstraints actualConstraintCount))
 
-    let distinctPoints = Map.fromList (map (\point -> (point, ())) points)
-        distinctPointCount = Map.size distinctPoints
+    let distinctPointCount = Set.size (V.foldl' (flip Set.insert) Set.empty points)
     unless (distinctPointCount == vertexCount) (throwE (DuplicateSerializedCoordinates vertexCount distinctPointCount))
 
     let pointXStore = fromLocalVector 0 pointXs
@@ -271,7 +350,7 @@
         , triPointY = pointYStore
         , triPointIndex = buildPointIndex pointXStore pointYStore
         , triVertexOut = fromLocalVector maxBound vertexOut
-        , triVertexData = boxedFromVector Nothing vertexDataVector
+        , triVertexData = boxedFromVector vertexDefault vertexDataVector
         , triHalfTopology = topologyStore
         , triDirectedData = boxedFromVector (Just (defaultDirectedEdgeData defaults)) directedDataVector
         , triUndirectedData = boxedFromVector (Just (defaultUndirectedEdgeData defaults)) undirectedDataVector
@@ -296,31 +375,84 @@
 modeTag Unconstrained = 0
 modeTag Constrained = 1
 
-putCount :: Word64 -> Put
-putCount = putWord64be
-
-getCount :: Decoder Int
-getCount = do
-  raw <- lift getWord64be
-  when (raw > fromIntegral (maxBound :: Int)) (throwE (EncodedCountExceedsInt raw))
-  pure (fromIntegral raw)
+validateStructuralPrefix
+  :: DecodingBudget
+  -> Word64
+  -> Word64
+  -> Word64
+  -> Word64
+  -> Decoder ()
+validateStructuralPrefix budget vertexCount directedEdgeCount faceCount constraintCount = do
+  traverse_
+    (uncurry validateEncodedCount)
+    [ (SerializedVertexCount, vertexCount)
+    , (SerializedDirectedEdgeCount, directedEdgeCount)
+    , (SerializedFaceCount, faceCount)
+    , (SerializedConstraintCount, constraintCount)
+    ]
+  unless (even directedEdgeCount) $
+    throwE (SerializedDirectedEdgeCountOdd directedEdgeCount)
+  unless (faceCount >= 1) (throwE SerializedMissingOuterFace)
+  let undirectedEdgeCount = directedEdgeCount `quot` 2
+  unless (constraintCount <= undirectedEdgeCount) $
+    throwE
+      ( SerializedConstraintCountExceedsEdges
+          constraintCount
+          undirectedEdgeCount
+      )
+  unless
+    (planarCardinalityHolds vertexCount directedEdgeCount faceCount)
+    ( throwE
+        ( SerializedPlanarCardinalityMismatch
+            vertexCount
+            directedEdgeCount
+            faceCount
+        )
+    )
+  let sectionElements =
+        4 * vertexCount
+          + 6 * directedEdgeCount
+          + 2 * faceCount
+      maximumElements = decodingMaximumSectionElements budget
+  when (sectionElements > maximumElements) $
+    throwE (DecodedSectionBudgetExceeded sectionElements maximumElements)
 
-putUVector :: U.Unbox a => (a -> Put) -> U.Vector a -> Put
-putUVector putElement values = do
-  putCount (fromIntegral (U.length values))
-  U.mapM_ putElement values
+validateEncodedCount :: SerializedCountKind -> Word64 -> Decoder ()
+validateEncodedCount kind count = do
+  when (count > fromIntegral (maxBound :: Int)) $
+    throwE (EncodedCountExceedsInt kind count)
+  when (count > maximumPackedElementCount) $
+    throwE
+      ( EncodedCountExceedsPackedIndex
+          kind
+          count
+          maximumPackedElementCount
+      )
 
-getUVector :: U.Unbox a => Decoder a -> Decoder (U.Vector a)
-getUVector getElement = do
-  count <- getCount
-  U.fromList <$> replicateM count getElement
+-- Each resident handle is a Word32 with @maxBound@ withheld as the optional
+-- no-index marker. A count may include every remaining representable handle.
+maximumPackedElementCount :: Word64
+maximumPackedElementCount = fromIntegral indexLimit + 1
 
-putBoxedVector :: (a -> Put) -> V.Vector a -> Put
-putBoxedVector putElement values = do
-  putCount (fromIntegral (V.length values))
-  V.mapM_ putElement values
+-- Every resident triangulation is a connected planar straight-line graph.
+-- Empty input has only the outer face; a bounded-face-free nonempty graph is the
+-- collinear chain; otherwise Euler's law determines the edge count. All
+-- arithmetic is safe after 'validateEncodedCount' bounds each term to the
+-- packed-index domain.
+planarCardinalityHolds :: Word64 -> Word64 -> Word64 -> Bool
+planarCardinalityHolds vertexCount directedEdgeCount faceCount
+  | vertexCount == 0 = directedEdgeCount == 0 && faceCount == 1
+  | faceCount == 1 = directedEdgeCount == 2 * (vertexCount - 1)
+  | vertexCount < 3 = False
+  | otherwise =
+      directedEdgeCount == 2 * (vertexCount + faceCount - 2)
 
-getBoxedVector :: Decoder a -> Decoder (V.Vector a)
-getBoxedVector getElement = do
-  count <- getCount
-  V.fromList <$> replicateM count getElement
+-- The packed-index proof above bounds every term far below Word64 overflow.
+-- Payloads and defaults have no format-level lower bound because lawful
+-- 'Binary' decoders such as that for @()@ may consume zero bytes.
+minimumFixedBodyBytes :: Word64 -> Word64 -> Word64 -> Word64
+minimumFixedBodyBytes vertexCount directedEdgeCount faceCount =
+  20 * vertexCount
+    + 16 * directedEdgeCount
+    + 4 * faceCount
+    + directedEdgeCount `quot` 2
diff --git a/test/algebra/Main.hs b/test/algebra/Main.hs
--- a/test/algebra/Main.hs
+++ b/test/algebra/Main.hs
@@ -1,9 +1,15 @@
 module Main (main) where
 
 import qualified Moonlight.Triangulation.AlgebraSpec as AlgebraSpec
+import qualified Moonlight.Triangulation.MinkowskiSpec as MinkowskiSpec
+import qualified Moonlight.Triangulation.RegionAlgebraSpec as RegionAlgebraSpec
 import qualified Moonlight.Triangulation.ScheduleAgreementSpec as ScheduleAgreementSpec
+import qualified Moonlight.Triangulation.ValuationSpec as ValuationSpec
 
 main :: IO ()
 main = do
   AlgebraSpec.tests
+  RegionAlgebraSpec.tests
+  ValuationSpec.tests
+  MinkowskiSpec.tests
   ScheduleAgreementSpec.tests
diff --git a/test/algebra/Moonlight/Triangulation/AlgebraFixtures.hs b/test/algebra/Moonlight/Triangulation/AlgebraFixtures.hs
--- a/test/algebra/Moonlight/Triangulation/AlgebraFixtures.hs
+++ b/test/algebra/Moonlight/Triangulation/AlgebraFixtures.hs
@@ -4,7 +4,15 @@
   ( Mesh
   , PointMesh
   , meshOf
+  , inputOrderedMeshOf
   , pointMeshOf
+  , integerPoint
+  , rectangleComponent
+  , rectangleRegion
+  , polygonRegion
+  , annulusRegion
+  , insideLayer
+  , overlayRegions
   , operands
   , separatedOperands
   , cocircularRing
@@ -25,28 +33,40 @@
   ) where
 
 import Data.List (sortBy)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.Map.Strict as Map
+import Data.Maybe (fromMaybe)
 import Data.Ord (comparing)
 import qualified Data.Set as Set
 import qualified Data.Vector as V
 import Data.Word (Word64)
 import Moonlight.Triangulation
   ( DelaunayTriangulation
+  , OverlayResult
+  , PlanarLayer
+  , PlanarRegion
   , Point (Point)
   , VertexId
   , buildTriangulation
   , canonicalize
   , delaunay
   , delaunayGeometry
+  , exactLoop
+  , mapVertices
   , numFaces
   , numUndirectedEdges
   , numVertices
+  , overlayLayers
+  , planarLayer
+  , planarRegion
+  , polygonComponent
   , undirectedEdges
   , undirectedEndpoints
   , unitElementDefaults
   , vertexPoint
   , vertices
   )
-import Support (requireRight)
+import Support (integerPoint, rectangleComponent, rectangleLoop, requireRight)
 
 -- | The carrier. Geometry and nothing else: no vertex payload to need a
 -- commutative combining rule, no element payloads to survive a rewrite that
@@ -56,7 +76,7 @@
 -- | The same carrier with its exact coordinate retained as the vertex
 -- annotation. Annotation-preserving set laws use it to construct their
 -- expected values without reaching below the public facade.
-type PointMesh = DelaunayTriangulation (Point)
+type PointMesh = DelaunayTriangulation Point
 
 -- ── operands ─────────────────────────────────────────────────────────────────
 
@@ -106,7 +126,7 @@
 -- they are geometrically identical and structurally distinct.
 operands :: IO [(String, Mesh)]
 operands = do
-  let sites name = maybe [] id (lookup name families)
+  let sites name = fromMaybe [] (lookup name families)
       lattice = sites "lattice"
   void' <- meshOf "void" []
   single <- meshOf "single" (sites "single")
@@ -153,10 +173,72 @@
 meshOf label points =
   requireRight ("build geometry " <> label) (delaunayGeometry (V.fromList points))
 
+-- | Build through the annotated entrance when a law deliberately needs the
+-- physical numbering induced by input order. Geometry-only construction is
+-- free to choose the cheaper numbering because it publishes no input mapping.
+inputOrderedMeshOf :: String -> [Point] -> IO Mesh
+inputOrderedMeshOf label points =
+  mapVertices (const ()) . buildTriangulation
+    <$> requireRight
+          ("build input-ordered geometry " <> label)
+          (delaunay unitElementDefaults (V.fromList points))
+
 pointMeshOf :: String -> [Point] -> IO PointMesh
 pointMeshOf label points =
   buildTriangulation
     <$> requireRight ("build " <> label) (delaunay unitElementDefaults (V.fromList points))
+
+rectangleRegion :: Integer -> Integer -> Integer -> Integer -> IO PlanarRegion
+rectangleRegion minimumX minimumY maximumX maximumY =
+  rectangleComponent minimumX minimumY maximumX maximumY
+    >>= requireRight "rectangle region" . planarRegion . (: [])
+
+polygonRegion :: [(Integer, Integer)] -> IO PlanarRegion
+polygonRegion coordinates =
+  case map (uncurry integerPoint) coordinates of
+    firstPoint : secondPoint : thirdPoint : remaining -> do
+      loop <-
+        requireRight
+          "polygon loop"
+          (exactLoop (firstPoint :| (secondPoint : thirdPoint : remaining)))
+      component <- requireRight "polygon component" (polygonComponent loop [])
+      requireRight "polygon region" (planarRegion [component])
+    _ -> fail "polygon fixture requires at least three points"
+
+annulusRegion
+  :: (Integer, Integer, Integer, Integer)
+  -> (Integer, Integer, Integer, Integer)
+  -> IO PlanarRegion
+annulusRegion
+  (outerMinX, outerMinY, outerMaxX, outerMaxY)
+  (holeMinX, holeMinY, holeMaxX, holeMaxY) = do
+    outer <- rectangleLoop outerMinX outerMinY outerMaxX outerMaxY
+    hole <-
+      requireRight
+        "annulus hole loop"
+        ( exactLoop
+            ( integerPoint holeMinX holeMinY
+                :| [ integerPoint holeMinX holeMaxY
+                   , integerPoint holeMaxX holeMaxY
+                   , integerPoint holeMaxX holeMinY
+                   ]
+            )
+        )
+    component <- requireRight "annulus component" (polygonComponent outer [hole])
+    requireRight "annulus region" (planarRegion [component])
+
+insideLayer :: PlanarRegion -> IO (PlanarLayer Bool)
+insideLayer region =
+  requireRight "inside layer" (planarLayer False (Map.singleton True region))
+
+overlayRegions
+  :: PlanarRegion
+  -> PlanarRegion
+  -> IO (OverlayResult Bool Bool)
+overlayRegions left right = do
+  leftLayer <- insideLayer left
+  rightLayer <- insideLayer right
+  requireRight "region algebra overlay" (overlayLayers leftLayer rightLayer)
 
 pointsOf :: [(Double, Double)] -> [Point]
 pointsOf keys = [Point x y | (x, y) <- keys]
diff --git a/test/algebra/Moonlight/Triangulation/AlgebraSpec.hs b/test/algebra/Moonlight/Triangulation/AlgebraSpec.hs
--- a/test/algebra/Moonlight/Triangulation/AlgebraSpec.hs
+++ b/test/algebra/Moonlight/Triangulation/AlgebraSpec.hs
@@ -55,6 +55,7 @@
   , collinearSites
   , dedupeAscending
   , edgeKeys
+  , inputOrderedMeshOf
   , latticeSites
   , meshOf
   , operands
@@ -107,8 +108,10 @@
   legacy <-
     mapVertices (const ()) . buildTriangulation
       <$> requireRight "annotated point construction" (delaunay unitElementDefaults coordinates)
-  unless (geometry == legacy) $
-    fail "delaunayGeometry disagreed with the existing point construction"
+  assertMeshEquivalent
+    "geometry-only construction agrees with annotated point construction"
+    legacy
+    geometry
   annotated <-
     buildTriangulation
       <$> requireRight
@@ -186,10 +189,10 @@
 testJoinGeneralPathIdempotent :: IO ()
 testJoinGeneralPathIdempotent = do
   let base = randomSites 0xA11CE 40
-  first <- meshOf "order-1" base
-  second <- meshOf "order-2" (scramble 0x1111 base)
-  third <- meshOf "order-3" (scramble 0x2222 base)
-  fourth <- meshOf "order-4" (reverse base)
+  first <- inputOrderedMeshOf "order-1" base
+  second <- inputOrderedMeshOf "order-2" (scramble 0x1111 base)
+  third <- inputOrderedMeshOf "order-3" (scramble 0x2222 base)
+  fourth <- inputOrderedMeshOf "order-4" (reverse base)
   unless (first /= second && second /= third && third /= fourth) $
     fail "general-path idempotence: the four builds are not distinct values, so the shortcut is not disarmed"
   joined <- requireRight "general-path union" (union first second)
@@ -283,7 +286,7 @@
     \(name, base) -> do
       built <-
         traverse
-          (\(order, sites) -> (,) order <$> meshOf (name <> "/" <> order) sites)
+          (\(order, sites) -> (,) order <$> inputOrderedMeshOf (name <> "/" <> order) sites)
           [ ("input", base)
           , ("ranked", dedupeAscending (sort [(x, y) | Point x y <- base]))
           , ("reversed", reverse base)
@@ -397,7 +400,7 @@
       let ranked = dedupeAscending (sort [(x, y) | Point x y <- base])
       meshes <-
         traverse
-          (\(order, sites) -> (,) order <$> meshOf (name <> "/" <> order) sites)
+          (\(order, sites) -> (,) order <$> inputOrderedMeshOf (name <> "/" <> order) sites)
           [ ("input", base)
           , ("ranked", ranked)
           , ("reversed", reverse base)
@@ -564,7 +567,7 @@
 testEmptySetIdentitiesPreserveVerbatim :: IO ()
 testEmptySetIdentitiesPreserveVerbatim = do
   let sites = scramble 0xE771D3 (randomSites 0xE771D4 48)
-  mesh <- meshOf "verbatim empty identity source" sites
+  mesh <- inputOrderedMeshOf "verbatim empty identity source" sites
   canonical <- requireRight "canonical verbatim empty identity source" (canonicalize mesh)
   unless (mesh /= canonical) $
     fail "verbatim empty identities: the source was already canonical, so this asserts nothing"
diff --git a/test/algebra/Moonlight/Triangulation/MinkowskiSpec.hs b/test/algebra/Moonlight/Triangulation/MinkowskiSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/algebra/Moonlight/Triangulation/MinkowskiSpec.hs
@@ -0,0 +1,226 @@
+-- | Exact convex convolution and residual-morphology laws.
+module Moonlight.Triangulation.MinkowskiSpec (tests) where
+
+import Moonlight.Triangulation.AlgebraFixtures
+  ( annulusRegion
+  , polygonRegion
+  , rectangleComponent
+  , rectangleRegion
+  )
+import Moonlight.Triangulation.Minkowski
+  ( MinkowskiOperation (..)
+  , closeWith
+  , convexMinkowskiSum
+  , convexPolygon
+  , erodeBy
+  , minkowskiGeneratedPieces
+  , minkowskiOperation
+  , minkowskiOverlayPasses
+  , minkowskiSum
+  , openWith
+  , polygonOffset
+  , structuringElement
+  )
+import Moonlight.Triangulation.Region
+  ( PlanarRegion
+  , emptyPlanarRegion
+  , exactLoopPoints
+  , planarRegion
+  , planarRegionComponents
+  , polygonOuterLoop
+  )
+import Moonlight.Triangulation.Valuation
+  ( exactAreaValue
+  , regionValuations
+  , valuationArea
+  )
+import Support (assertEqual, requireRight)
+
+tests :: IO ()
+tests = do
+  testConvexConvolution
+  testGeneralAddition
+  testConvexMorphology
+  testGeneralErosion
+  testHoledAndNeckedErosion
+  putStrLn "minkowski: ok"
+
+testConvexConvolution :: IO ()
+testConvexConvolution = do
+  leftComponent <- rectangleComponent 0 0 1 1
+  rightComponent <- rectangleComponent 0 0 1 1
+  left <-
+    requireRight
+      "left convex polygon"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop leftComponent)))
+  right <-
+    requireRight
+      "right convex polygon"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop rightComponent)))
+  let result = convexMinkowskiSum left right
+  assertRegionArea "convex square sum" 4 result
+  assertEqual
+    "convex convolution is commutative after canonical publication"
+    result
+    (convexMinkowskiSum right left)
+
+testGeneralAddition :: IO ()
+testGeneralAddition = do
+  first <- rectangleComponent 0 0 1 1
+  second <- rectangleComponent 3 0 4 1
+  disconnected <- requireRight "disconnected source" (planarRegion [first, second])
+  kernel <- rectangleRegion 0 0 1 1
+  (sumRegion, receipt) <-
+    requireRight "general disconnected sum" (minkowskiSum disconnected kernel)
+  assertEqual "general sum operation receipt" MinkowskiAddition (minkowskiOperation receipt)
+  assertEqual "general sum generated one piece per source component" 2 (minkowskiGeneratedPieces receipt)
+  assertEqual "general sum remains disconnected" 2 (length (planarRegionComponents sumRegion))
+  assertRegionArea "general disconnected sum" 8 sumRegion
+  (swapped, _) <- requireRight "swapped disconnected sum" (minkowskiSum kernel disconnected)
+  assertEqual "general Minkowski sum is commutative" sumRegion swapped
+  (annihilated, _) <-
+    requireRight "empty Minkowski annihilator" (minkowskiSum disconnected emptyPlanarRegion)
+  assertEqual "empty region annihilates Minkowski addition" emptyPlanarRegion annihilated
+  firstRegion <- requireRight "first distributive operand" (planarRegion [first])
+  secondRegion <- requireRight "second distributive operand" (planarRegion [second])
+  (firstSum, _) <- requireRight "first distributed sum" (minkowskiSum firstRegion kernel)
+  (secondSum, _) <- requireRight "second distributed sum" (minkowskiSum secondRegion kernel)
+  distributed <-
+    requireRight
+      "distributed union"
+      (planarRegion (planarRegionComponents firstSum <> planarRegionComponents secondSum))
+  assertEqual "Minkowski addition distributes over disjoint union" distributed sumRegion
+
+  associativityThird <- rectangleRegion (-2) 0 0 1
+  (leftPair, _) <- requireRight "associative left pair" (minkowskiSum firstRegion kernel)
+  (leftAssociated, _) <-
+    requireRight "left-associated Minkowski sum" (minkowskiSum leftPair associativityThird)
+  (rightPair, _) <- requireRight "associative right pair" (minkowskiSum kernel associativityThird)
+  (rightAssociated, _) <-
+    requireRight "right-associated Minkowski sum" (minkowskiSum firstRegion rightPair)
+  assertEqual "Minkowski addition is associative" leftAssociated rightAssociated
+  concave <-
+    polygonRegion
+      [ (0, 0), (3, 0), (3, 1), (1, 1), (1, 3), (0, 3) ]
+  (concaveSum, concaveReceipt) <-
+    requireRight "concave triangulated sum" (minkowskiSum concave kernel)
+  expectedConcaveSum <-
+    polygonRegion
+      [ (0, 0), (4, 0), (4, 2), (2, 2), (2, 4), (0, 4) ]
+  assertEqual "triangulated nonconvex sum" expectedConcaveSum concaveSum
+  if minkowskiOverlayPasses concaveReceipt > 0
+    then pure ()
+    else fail "nonconvex addition bypassed CDT decomposition and overlay union"
+
+testConvexMorphology :: IO ()
+testConvexMorphology = do
+  source <- rectangleRegion (-2) (-2) 2 2
+  kernelComponent <- rectangleComponent (-1) (-1) 1 1
+  kernelPolygon <-
+    requireRight
+      "centred square kernel"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop kernelComponent)))
+  element <- requireRight "centred structuring element" (structuringElement kernelPolygon)
+  (eroded, erosionReceipt) <- requireRight "convex erosion" (erodeBy element source)
+  expectedErosion <- rectangleRegion (-1) (-1) 1 1
+  assertEqual "convex support-half-plane erosion" expectedErosion eroded
+  assertEqual "convex erosion needs no overlay" 0 (minkowskiOverlayPasses erosionReceipt)
+  exactFitSource <- rectangleRegion (-1) (-1) 1 1
+  (lowerDimensionalResidual, _) <-
+    requireRight "exact-fit regularized erosion" (erodeBy element exactFitSource)
+  assertEqual
+    "point-only erosion residual regularizes to the empty polygonal region"
+    emptyPlanarRegion
+    lowerDimensionalResidual
+  (emptyResidual, _) <- requireRight "empty source erosion" (erodeBy element emptyPlanarRegion)
+  assertEqual "empty source erodes to empty" emptyPlanarRegion emptyResidual
+  (expanded, _) <- requireRight "convex offset" (polygonOffset element source)
+  expectedExpansion <- rectangleRegion (-3) (-3) 3 3
+  assertEqual "convex offset" expectedExpansion expanded
+  (opened, _) <- requireRight "convex opening" (openWith element source)
+  assertEqual "convex opening is idempotent on the square" source opened
+  (openedTwice, _) <- requireRight "second convex opening" (openWith element opened)
+  assertEqual "opening is idempotent" opened openedTwice
+  (closed, _) <- requireRight "convex closing" (closeWith element source)
+  assertEqual "convex closing is idempotent on the square" source closed
+  (closedTwice, _) <- requireRight "second convex closing" (closeWith element closed)
+  assertEqual "closing is idempotent" closed closedTwice
+  cornerKernelComponent <- rectangleComponent 0 0 1 1
+  cornerKernel <-
+    requireRight
+      "corner-anchored kernel"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop cornerKernelComponent)))
+  cornerElement <- requireRight "corner-anchored element" (structuringElement cornerKernel)
+  cornerSource <- rectangleRegion 0 0 4 4
+  (cornerErosion, _) <- requireRight "corner-anchored erosion" (erodeBy cornerElement cornerSource)
+  expectedCornerErosion <- rectangleRegion 0 0 3 3
+  assertEqual "kernel origin controls erosion translation" expectedCornerErosion cornerErosion
+
+testGeneralErosion :: IO ()
+testGeneralErosion = do
+  left <- rectangleComponent 0 0 4 4
+  right <- rectangleComponent 6 0 10 4
+  source <- requireRight "two-component erosion source" (planarRegion [left, right])
+  kernelComponent <- rectangleComponent (-1) (-1) 1 1
+  kernelPolygon <-
+    requireRight
+      "general erosion kernel"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop kernelComponent)))
+  element <- requireRight "general erosion element" (structuringElement kernelPolygon)
+  (eroded, receipt) <- requireRight "general erosion" (erodeBy element source)
+  expectedLeft <- rectangleComponent 1 1 3 3
+  expectedRight <- rectangleComponent 7 1 9 3
+  expected <- requireRight "expected general erosion" (planarRegion [expectedLeft, expectedRight])
+  assertEqual "general residual erosion" expected eroded
+  if minkowskiOverlayPasses receipt > 0
+    then pure ()
+    else fail "general erosion bypassed the overlay candidate arrangement"
+
+testHoledAndNeckedErosion :: IO ()
+testHoledAndNeckedErosion = do
+  kernelComponent <- rectangleComponent (-1) (-1) 1 1
+  kernelPolygon <-
+    requireRight
+      "holed erosion kernel"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop kernelComponent)))
+  element <- requireRight "holed erosion element" (structuringElement kernelPolygon)
+  sourceAnnulus <- annulusRegion (0, 0, 10, 10) (4, 4, 6, 6)
+  expectedAnnulus <- annulusRegion (1, 1, 9, 9) (3, 3, 7, 7)
+  (erodedAnnulus, _) <- requireRight "annulus erosion" (erodeBy element sourceAnnulus)
+  assertEqual "erosion shrinks the outer boundary and expands holes" expectedAnnulus erodedAnnulus
+
+  necked <-
+    polygonRegion
+      [ (0, 0)
+      , (8, 0)
+      , (8, 3)
+      , (12, 3)
+      , (12, 0)
+      , (20, 0)
+      , (20, 8)
+      , (12, 8)
+      , (12, 5)
+      , (8, 5)
+      , (8, 8)
+      , (0, 8)
+      ]
+  wideKernelComponent <- rectangleComponent (-2) (-2) 2 2
+  wideKernelPolygon <-
+    requireRight
+      "neck erosion kernel"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop wideKernelComponent)))
+  wideElement <- requireRight "neck erosion element" (structuringElement wideKernelPolygon)
+  (separated, _) <- requireRight "narrow-neck erosion" (erodeBy wideElement necked)
+  expectedLeft <- rectangleComponent 2 2 6 6
+  expectedRight <- rectangleComponent 14 2 18 6
+  expectedSeparated <-
+    requireRight "expected separated erosion" (planarRegion [expectedLeft, expectedRight])
+  assertEqual "erosion removes a neck narrower than the kernel" expectedSeparated separated
+
+assertRegionArea :: String -> Integer -> PlanarRegion -> IO ()
+assertRegionArea label expected region = do
+  valuations <- requireRight label (regionValuations region)
+  assertEqual
+    label
+    (fromInteger expected)
+    (exactAreaValue (valuationArea valuations))
diff --git a/test/algebra/Moonlight/Triangulation/RegionAlgebraSpec.hs b/test/algebra/Moonlight/Triangulation/RegionAlgebraSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/algebra/Moonlight/Triangulation/RegionAlgebraSpec.hs
@@ -0,0 +1,90 @@
+-- | Facade-only laws for exact planar Boolean composition.
+module Moonlight.Triangulation.RegionAlgebraSpec (tests) where
+
+import Moonlight.Triangulation
+import Moonlight.Triangulation.AlgebraFixtures
+  ( overlayRegions
+  , rectangleComponent
+  , rectangleRegion
+  )
+import Support (assertEqual, requireRight)
+
+tests :: IO ()
+tests = do
+  testRegionBooleanLaws
+  testFacadeComposition
+  putStrLn "region algebra: ok"
+
+testRegionBooleanLaws :: IO ()
+testRegionBooleanLaws = do
+  left <- rectangleRegion 0 0 2 2
+  middle <- rectangleRegion 1 0 3 2
+  right <- rectangleRegion 2 0 4 2
+  expectedUnion <- rectangleRegion 0 0 3 2
+  expectedIntersection <- rectangleRegion 1 0 2 2
+  expectedDifference <- rectangleRegion 0 0 1 2
+
+  leftUnionMiddle <- regionUnion left middle
+  middleUnionLeft <- regionUnion middle left
+  assertEqual "region union result" expectedUnion leftUnionMiddle
+  assertEqual "region union commutativity" leftUnionMiddle middleUnionLeft
+  assertEqual "region union idempotence" left =<< regionUnion left left
+  assertEqual
+    "region intersection result"
+    expectedIntersection
+    =<< regionIntersection left middle
+  assertEqual
+    "regularized region difference"
+    expectedDifference
+    =<< regionDifference left middle
+
+  leftAssociated <- regionUnion leftUnionMiddle right
+  middleUnionRight <- regionUnion middle right
+  rightAssociated <- regionUnion left middleUnionRight
+  assertEqual "region union associativity" leftAssociated rightAssociated
+
+testFacadeComposition :: IO ()
+testFacadeComposition = do
+  left <- rectangleRegion 0 0 2 2
+  right <- rectangleRegion 1 0 3 2
+  overlay <- overlayRegions left right
+  selectedUnion <-
+    requireRight
+      "facade selected region"
+      (overlaySelectedRegion (uncurry (||)) overlay)
+  valuations <- requireRight "facade region valuations" (regionValuations selectedUnion)
+  assertEqual
+    "facade valuation digest"
+    (1, 6)
+    ( eulerCharacteristicValue (valuationEuler valuations)
+    , exactAreaValue (valuationArea valuations)
+    )
+
+  kernelComponent <- rectangleComponent 0 0 1 1
+  kernel <-
+    requireRight
+      "facade convex kernel"
+      (convexPolygon (exactLoopPoints (polygonOuterLoop kernelComponent)))
+  element <- requireRight "facade structuring element" (structuringElement kernel)
+  (expanded, receipt) <- requireRight "facade polygon offset" (polygonOffset element selectedUnion)
+  expectedExpanded <- rectangleRegion 0 0 4 3
+  assertEqual "facade morphology result" expectedExpanded expanded
+  assertEqual "facade morphology receipt" MinkowskiAddition (minkowskiOperation receipt)
+
+regionUnion :: PlanarRegion -> PlanarRegion -> IO PlanarRegion
+regionUnion = combineRegions (uncurry (||))
+
+regionIntersection :: PlanarRegion -> PlanarRegion -> IO PlanarRegion
+regionIntersection = combineRegions (uncurry (&&))
+
+regionDifference :: PlanarRegion -> PlanarRegion -> IO PlanarRegion
+regionDifference = combineRegions (\(insideLeft, insideRight) -> insideLeft && not insideRight)
+
+combineRegions
+  :: ((Bool, Bool) -> Bool)
+  -> PlanarRegion
+  -> PlanarRegion
+  -> IO PlanarRegion
+combineRegions selected left right = do
+  overlay <- overlayRegions left right
+  requireRight "region Boolean publication" (overlaySelectedRegion selected overlay)
diff --git a/test/algebra/Moonlight/Triangulation/ValuationSpec.hs b/test/algebra/Moonlight/Triangulation/ValuationSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/algebra/Moonlight/Triangulation/ValuationSpec.hs
@@ -0,0 +1,271 @@
+-- | Exact intrinsic-volume fixtures and common-subdivision inclusion-exclusion.
+module Moonlight.Triangulation.ValuationSpec (tests) where
+
+import Moonlight.Triangulation.AlgebraFixtures
+  ( annulusRegion
+  , insideLayer
+  , polygonRegion
+  , rectangleComponent
+  , rectangleRegion
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactRational )
+import Moonlight.Triangulation.Overlay
+  ( overlayClosedIntersection
+  , overlayClosedUnion
+  , overlayLayers
+  , overlaySelectedRegion
+  )
+import Moonlight.Triangulation.Region
+  ( PlanarRegion
+  , emptyPlanarRegion
+  , planarRegion
+  )
+import Moonlight.Triangulation.Valuation
+  ( CertifiedInterval (..)
+  , ExactLengthTerm
+  , PlanarValuations
+  , ValuationError (ValuationCellSetNotPureRegion)
+  , cellSetPerimeter
+  , cellValuations
+  , exactAreaValue
+  , exactLengthBounds
+  , exactLengthExpression
+  , exactLengthTerms
+  , eulerCharacteristicValue
+  , lengthCoefficient
+  , regionPerimeter
+  , regionValuations
+  , squaredLength
+  , valuationArea
+  , valuationEuler
+  , valuationIntrinsic1
+  )
+import Support (assertEqual, requireRight)
+
+tests :: IO ()
+tests = do
+  testRegionGoldenValues
+  testClosedCellInclusionExclusion
+  testDimensionalCellFixtures
+  testMetricInvariance
+  putStrLn "valuation: ok"
+
+testRegionGoldenValues :: IO ()
+testRegionGoldenValues = do
+  assertRegionValuations "empty" emptyPlanarRegion 0 0 []
+
+  unit <- rectangleRegion 0 0 1 1
+  assertRegionValuations "unit square" unit 1 1 [(2, 1)]
+  unitPerimeter <- requireRight "unit-square perimeter" (regionPerimeter unit)
+  assertLength "unit-square perimeter" [(4, 1)] (exactLengthTerms (exactLengthExpression unitPerimeter))
+  assertContains "unit-square perimeter bounds" 4 (exactLengthBounds unitPerimeter)
+
+  annulus <- annulusRegion (0, 0, 3, 3) (1, 1, 2, 2)
+  assertRegionValuations "annulus" annulus 0 8 [(2, 1), (2, 9)]
+
+  lowerLeft <- rectangleComponent 0 0 1 1
+  upperRight <- rectangleComponent 1 1 2 2
+  cornerTouch <- requireRight "corner-touch region" (planarRegion [lowerLeft, upperRight])
+  assertRegionValuations "corner-touch squares" cornerTouch 1 2 [(4, 1)]
+
+  right <- rectangleComponent 1 0 2 1
+  edgeTouch <- requireRight "edge-touch region" (planarRegion [lowerLeft, right])
+  assertRegionValuations "edge-sharing squares" edgeTouch 1 2 [(3, 1)]
+
+testClosedCellInclusionExclusion :: IO ()
+testClosedCellInclusionExclusion = do
+  leftRegion <- rectangleRegion 0 0 1 1
+  rightRegion <- rectangleRegion 1 0 2 1
+  leftLayer <- insideLayer leftRegion
+  rightLayer <- insideLayer rightRegion
+  overlay <- requireRight "edge-sharing valuation overlay" (overlayLayers leftLayer rightLayer)
+  left <- requireRight "left closed cells" (overlayClosedUnion id (const False) overlay)
+  right <- requireRight "right closed cells" (overlayClosedUnion (const False) id overlay)
+  union <- requireRight "union closed cells" (overlayClosedUnion id id overlay)
+  intersection <-
+    requireRight
+      "intersection closed cells"
+      (overlayClosedIntersection id id overlay)
+  leftValues <- requireRight "left valuations" (cellValuations left)
+  rightValues <- requireRight "right valuations" (cellValuations right)
+  unionValues <- requireRight "union valuations" (cellValuations union)
+  sharedEdgeValues <- requireRight "intersection valuations" (cellValuations intersection)
+  assertEqual
+    "Euler inclusion-exclusion retains the shared edge"
+    (1, 1, 1, 1)
+    ( eulerCharacteristicValue (valuationEuler leftValues)
+    , eulerCharacteristicValue (valuationEuler rightValues)
+    , eulerCharacteristicValue (valuationEuler unionValues)
+    , eulerCharacteristicValue (valuationEuler sharedEdgeValues)
+    )
+  assertEqual
+    "area inclusion-exclusion retains zero-dimensional measure"
+    ( 1
+    , 1
+    , 2
+    , 0
+    )
+    ( areaExact leftValues
+    , areaExact rightValues
+    , areaExact unionValues
+    , areaExact sharedEdgeValues
+    )
+  assertLength
+    "left intrinsic one-volume"
+    [(2, 1)]
+    (exactLengthTerms (exactLengthExpression (valuationIntrinsic1 leftValues)))
+  assertLength
+    "right intrinsic one-volume"
+    [(2, 1)]
+    (exactLengthTerms (exactLengthExpression (valuationIntrinsic1 rightValues)))
+  assertLength
+    "union intrinsic one-volume"
+    [(3, 1)]
+    (exactLengthTerms (exactLengthExpression (valuationIntrinsic1 unionValues)))
+  assertLength
+    "shared-edge intrinsic one-volume"
+    [(1, 1)]
+    (exactLengthTerms (exactLengthExpression (valuationIntrinsic1 sharedEdgeValues)))
+  case cellSetPerimeter intersection of
+    Left ValuationCellSetNotPureRegion -> pure ()
+    other ->
+      fail
+        ( "an isolated selected edge was accepted as a perimeter: "
+            <> show other
+        )
+  published <-
+    requireRight
+      "published edge-sharing union"
+      (overlaySelectedRegion (uncurry (||)) overlay)
+  publishedValues <- requireRight "published union valuations" (regionValuations published)
+  assertEqual
+    "cell and published region valuations agree"
+    (valuationDigest unionValues)
+    (valuationDigest publishedValues)
+
+testDimensionalCellFixtures :: IO ()
+testDimensionalCellFixtures = do
+  disjoint <- intersectionValues (0, 0, 1, 1) (2, 0, 3, 1)
+  assertEqual
+    "empty intersection valuations"
+    (0, 0, [])
+    (exactDigest disjoint)
+
+  point <- intersectionValues (0, 0, 1, 1) (1, 1, 2, 2)
+  assertEqual
+    "point intersection valuations"
+    (1, 0, [])
+    (exactDigest point)
+
+  area <- intersectionValues (0, 0, 2, 2) (1, 0, 3, 2)
+  assertEqual
+    "two-dimensional intersection valuations"
+    ( 1
+    , 2
+    , [ (1, 1)
+      , (1, 4)
+      ]
+    )
+    (exactDigest area)
+
+testMetricInvariance :: IO ()
+testMetricInvariance = do
+  original <- polygonRegion [(0, 0), (1, 0), (0, 1)]
+  translated <- polygonRegion [(5, -3), (6, -3), (5, -2)]
+  quarterTurned <- polygonRegion [(0, 0), (0, 1), (-1, 0)]
+  originalValues <- requireRight "original triangle valuations" (regionValuations original)
+  translatedValues <- requireRight "translated triangle valuations" (regionValuations translated)
+  quarterTurnedValues <- requireRight "quarter-turned triangle valuations" (regionValuations quarterTurned)
+  assertEqual "translation invariance" (exactDigest originalValues) (exactDigest translatedValues)
+  assertEqual "quarter-turn invariance" (exactDigest originalValues) (exactDigest quarterTurnedValues)
+  perimeter <- requireRight "irrational triangle perimeter" (regionPerimeter original)
+  assertContains
+    "certified radical perimeter"
+    (2 + sqrt 2)
+    (exactLengthBounds perimeter)
+
+intersectionValues
+  :: (Integer, Integer, Integer, Integer)
+  -> (Integer, Integer, Integer, Integer)
+  -> IO PlanarValuations
+intersectionValues leftBounds rightBounds = do
+  leftRegion <- uncurryRectangle leftBounds
+  rightRegion <- uncurryRectangle rightBounds
+  leftLayer <- insideLayer leftRegion
+  rightLayer <- insideLayer rightRegion
+  overlay <- requireRight "dimensional valuation overlay" (overlayLayers leftLayer rightLayer)
+  selected <-
+    requireRight
+      "dimensional closed intersection"
+      (overlayClosedIntersection id id overlay)
+  requireRight "dimensional cell valuations" (cellValuations selected)
+
+uncurryRectangle
+  :: (Integer, Integer, Integer, Integer)
+  -> IO PlanarRegion
+uncurryRectangle (minimumX, minimumY, maximumX, maximumY) =
+  rectangleRegion minimumX minimumY maximumX maximumY
+
+assertRegionValuations
+  :: String
+  -> PlanarRegion
+  -> Int
+  -> Integer
+  -> [(Integer, Integer)]
+  -> IO ()
+assertRegionValuations label region expectedEuler expectedArea expectedLength = do
+  values <- requireRight label (regionValuations region)
+  assertEqual
+    (label <> " Euler")
+    expectedEuler
+    (eulerCharacteristicValue (valuationEuler values))
+  assertEqual
+    (label <> " area")
+    (fromInteger expectedArea)
+    (exactAreaValue (valuationArea values))
+  assertLength
+    (label <> " intrinsic one-volume")
+    expectedLength
+    (exactLengthTerms (exactLengthExpression (valuationIntrinsic1 values)))
+
+assertLength :: String -> [(Integer, Integer)] -> [ExactLengthTerm] -> IO ()
+assertLength label expected actual =
+  assertEqual
+    label
+    [ (fromInteger coefficient, fromInteger square)
+    | (coefficient, square) <- expected
+    ]
+    [ (lengthCoefficient term, squaredLength term)
+    | term <- actual
+    ]
+
+assertContains :: String -> Double -> CertifiedInterval -> IO ()
+assertContains label expected interval =
+  if intervalLower interval <= expected && expected <= intervalUpper interval
+    then pure ()
+    else fail (label <> ": interval does not contain " <> show expected <> ": " <> show interval)
+
+valuationDigest
+  :: PlanarValuations
+  -> (Int, ExactRational, CertifiedInterval)
+valuationDigest values =
+  ( eulerCharacteristicValue (valuationEuler values)
+  , exactAreaValue (valuationArea values)
+  , exactLengthBounds (valuationIntrinsic1 values)
+  )
+
+exactDigest
+  :: PlanarValuations
+  -> (Int, ExactRational, [(ExactRational, ExactRational)])
+exactDigest values =
+  ( eulerCharacteristicValue (valuationEuler values)
+  , exactAreaValue (valuationArea values)
+  , [ (lengthCoefficient term, squaredLength term)
+    | term <- exactLengthTerms (exactLengthExpression (valuationIntrinsic1 values))
+    ]
+  )
+
+areaExact :: PlanarValuations -> ExactRational
+areaExact values =
+  exactAreaValue (valuationArea values)
diff --git a/test/coherence/Main.hs b/test/coherence/Main.hs
--- a/test/coherence/Main.hs
+++ b/test/coherence/Main.hs
@@ -4,10 +4,13 @@
 module Main (main) where
 
 import Moonlight.Triangulation.AlgebraSpec ()
+import Moonlight.Triangulation.MinkowskiSpec ()
 import Moonlight.Triangulation.NativeSpec ()
 import Moonlight.Triangulation.ParallelSpec ()
+import Moonlight.Triangulation.RegionAlgebraSpec ()
 import Moonlight.Triangulation.ScheduleAgreementSpec ()
 import Moonlight.Triangulation.SerializationSpec ()
+import Moonlight.Triangulation.ValuationSpec ()
 
 main :: IO ()
 main = pure ()
diff --git a/test/native/Main.hs b/test/native/Main.hs
--- a/test/native/Main.hs
+++ b/test/native/Main.hs
@@ -1,6 +1,13 @@
 module Main (main) where
 
+import qualified Moonlight.Triangulation.ExactEmbeddingSpec as ExactEmbeddingSpec
 import qualified Moonlight.Triangulation.NativeSpec as NativeSpec
+import qualified Moonlight.Triangulation.OverlaySpec as OverlaySpec
+import qualified Moonlight.Triangulation.RegionSpec as RegionSpec
 
 main :: IO ()
-main = NativeSpec.tests
+main =
+  NativeSpec.tests
+    >> ExactEmbeddingSpec.tests
+    >> OverlaySpec.tests
+    >> RegionSpec.tests
diff --git a/test/native/Moonlight/Triangulation/ExactEmbeddingSpec.hs b/test/native/Moonlight/Triangulation/ExactEmbeddingSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/native/Moonlight/Triangulation/ExactEmbeddingSpec.hs
@@ -0,0 +1,591 @@
+{-# LANGUAGE NumericUnderscores #-}
+
+-- | Focused exact-geometry and local-embedding milestone acceptance.
+module Moonlight.Triangulation.ExactEmbeddingSpec (tests) where
+
+import Control.DeepSeq (force)
+import Control.Exception (evaluate)
+import Control.Monad (unless)
+import Data.Bits (xor)
+import Data.Foldable (traverse_)
+import Data.List (sort)
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.Map.Strict as Map
+import Data.Word (Word64)
+import GHC.Stats (allocated_bytes, getRTSStats, getRTSStatsEnabled)
+import Moonlight.Triangulation.Exact
+  ( ExactGeometryError (..)
+  , ExactIntersectionError (..)
+  , ExactPoint
+  , exactLineIntersection
+  , exactPoint
+  , exactPointCoordinates
+  , exactPointFromPoint
+  , exactPointToEmbeddingCandidate
+  , exactSegment
+  , exactSegmentRelation
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( ExactArithmeticError (..)
+  , ExactRational
+  , exactDivide
+  , exactRational
+  , exactRationalDenominator
+  , exactRationalFromDouble
+  , exactRationalNumerator
+  , exactSignum
+  )
+import Moonlight.Triangulation.Internal.Overlay.Embedding
+  ( DraftIncidence (..)
+  , DraftNeighborhood (..)
+  , DraftSegmentId (..)
+  , DraftSourceId (..)
+  , DraftVertexId (..)
+  , EmbeddingObligation (..)
+  , ExactArrangementDraft (..)
+  , LocalEmbeddingCertificate (..)
+  , OverlayEmbeddingObstruction (..)
+  , certifyLocalEmbedding
+  , residualUndischargedObligations
+  )
+import Moonlight.Triangulation.Math
+  ( SegmentRelation (..)
+  , allSegmentRelations
+  , segmentRelation
+  )
+import Moonlight.Triangulation.Types
+  ( CoordinateError (..)
+  , Point (..)
+  , PointValidationError (..)
+  )
+import Support (assertEqual, integerPoint, requireRight)
+import System.Mem (performGC)
+
+-- | Run the exact geometry, embedding, and frozen binary64 acceptance actions.
+tests :: IO ()
+tests =
+  sequence_
+    [ testExactNonDyadicCrossing
+    , testAllSegmentRelationsAgree
+    , testSubUlpExactVertexCluster
+    , testContactDuplicateAndOverlapAgreement
+    , testVerticalNestedOverlapRefusal
+    , testCoordinateRefusalsAndRoundtrip
+    , testExactArithmeticReceipt
+    , testEmbeddingAdmissionReceipt
+    , testOrdinaryDraftCertificate
+    , testFrozenBinary64RelationOracle
+    ]
+
+testExactNonDyadicCrossing :: IO ()
+testExactNonDyadicCrossing = do
+  firstSegment <-
+    requireRight
+      "first non-dyadic crossing segment"
+      (exactSegment (integerPoint 0 0) (integerPoint 1 1))
+  secondSegment <-
+    requireRight
+      "second non-dyadic crossing segment"
+      (exactSegment (integerPoint 0 1) (integerPoint 2 0))
+  crossing <-
+    requireRight
+      "non-dyadic exact line intersection"
+      (exactLineIntersection firstSegment secondSegment)
+  let (crossingX, crossingY) = exactPointCoordinates crossing
+  assertRatio "non-dyadic crossing x" 2 3 crossingX
+  assertRatio "non-dyadic crossing y" 2 3 crossingY
+  putStrLn
+    ( "exact non-dyadic crossing coordinates: "
+        <> show (exactRationalNumerator crossingX, exactRationalDenominator crossingX)
+        <> ", "
+        <> show (exactRationalNumerator crossingY, exactRationalDenominator crossingY)
+    )
+
+testAllSegmentRelationsAgree :: IO ()
+testAllSegmentRelationsAgree = do
+  observed <- traverse observeExactAndRounded handFixtures
+  traverse_ assertExpectedAndAgreement observed
+  assertEqual
+    "all exact segment relations covered"
+    allSegmentRelations
+    (sort (map observedExactRelation observed))
+
+testSubUlpExactVertexCluster :: IO ()
+testSubUlpExactVertexCluster = do
+  let base = 2 ^ (52 :: Int)
+  firstCoordinate <- requireRight "first sub-ulp coordinate" (exactRational (base * 4 + 1) 4)
+  secondCoordinate <- requireRight "second sub-ulp coordinate" (exactRational (base * 4 + 2) 4)
+  thirdCoordinate <- requireRight "third sub-ulp coordinate" (exactRational (base * 4 + 3) 4)
+  firstCrossing <- exactCrossingAt firstCoordinate
+  secondCrossing <- exactCrossingAt secondCoordinate
+  thirdCrossing <- exactCrossingAt thirdCoordinate
+  let draft =
+        emptyDraft
+          { draftVertices =
+              Map.fromList
+                [ (DraftVertexId 0, firstCrossing)
+                , (DraftVertexId 1, secondCrossing)
+                , (DraftVertexId 2, thirdCrossing)
+                ]
+          }
+  assertLeftContains
+    "sub-ulp exact vertex cluster"
+    isRoundedCollision
+    (certifyLocalEmbedding draft)
+
+testContactDuplicateAndOverlapAgreement :: IO ()
+testContactDuplicateAndOverlapAgreement =
+  traverse_ assertContactAgreement contactFixtures
+
+testVerticalNestedOverlapRefusal :: IO ()
+testVerticalNestedOverlapRefusal = do
+  let base = 2 ^ (52 :: Int)
+      zero = 0
+      lower = fromInteger base
+      upper = fromInteger (base + 1)
+  innerLower <- requireRight "vertical inner lower" (exactRational (base * 3 + 1) 3)
+  innerUpper <- requireRight "vertical inner upper" (exactRational (base * 3 + 2) 3)
+  let outerFrom = exactPoint zero lower
+      outerTo = exactPoint zero upper
+      innerFrom = exactPoint zero innerLower
+      innerTo = exactPoint zero innerUpper
+      outerSegmentId = DraftSegmentId 0
+      innerSegmentId = DraftSegmentId 1
+      draft =
+        emptyDraft
+          { draftVertices =
+              Map.fromList
+                [ (DraftVertexId 0, outerFrom)
+                , (DraftVertexId 1, outerTo)
+                , (DraftVertexId 2, innerFrom)
+                , (DraftVertexId 3, innerTo)
+                ]
+          , draftSegments =
+              Map.fromList
+                [ (outerSegmentId, (DraftVertexId 0, DraftVertexId 1))
+                , (innerSegmentId, (DraftVertexId 2, DraftVertexId 3))
+                ]
+          , draftIncidences =
+              [ DraftIncidence
+                  outerSegmentId
+                  innerSegmentId
+                  SegmentsCollinearlyOverlap
+              ]
+          }
+  assertEqual
+    "vertical nested overlap remains exact"
+    SegmentsCollinearlyOverlap
+    (exactSegmentRelation outerFrom outerTo innerFrom innerTo)
+  assertLeftContains
+    "vertical nested overlap rounded incidence"
+    isIncidenceChange
+    (certifyLocalEmbedding draft)
+
+testCoordinateRefusalsAndRoundtrip :: IO ()
+testCoordinateRefusalsAndRoundtrip = do
+  assertEqual
+    "zero exact denominator refusal"
+    (Left ExactZeroDenominator)
+    (exactRational 1 0)
+  assertEqual
+    "zero exact divisor refusal"
+    (Left ExactZeroDivisor)
+    (exactDivide 1 0)
+  normalized <- requireRight "normalized exact rational" (exactRational (-2) (-4))
+  assertRatio "normalized exact rational" 1 2 normalized
+  canonicalZero <- requireRight "canonical exact zero" (exactRational 0 (-7))
+  assertRatio "canonical exact zero" 0 1 canonicalZero
+  assertEqual
+    "NaN exact rational refusal"
+    (Left ExactNaNInput)
+    (exactRationalFromDouble (0 / 0))
+  assertEqual
+    "infinite exact rational refusal"
+    (Left ExactInfiniteInput)
+    (exactRationalFromDouble (1 / 0))
+  assertEqual
+    "NaN exact point refusal"
+    (Left (InvalidPointX CoordinateNaN))
+    (exactPointFromPoint (Point (0 / 0) 0))
+  assertEqual
+    "infinite exact point refusal"
+    (Left (InvalidPointX CoordinateInfinite))
+    (exactPointFromPoint (Point (1 / 0) 0))
+  let unprojectablePoint =
+        exactPoint
+          (fromInteger (10 ^ (400 :: Int)))
+          0
+      unprojectableDraft =
+        emptyDraft
+          { draftVertices =
+              Map.singleton (DraftVertexId 0) unprojectablePoint
+          }
+  assertLeftContains
+    "unprojectable exact draft vertex"
+    isProjectionRefusal
+    (certifyLocalEmbedding unprojectableDraft)
+  let finitePoint = Point 1.25 (-2.5)
+  exactFinite <- requireRight "finite point exact conversion" (exactPointFromPoint finitePoint)
+  projectedFinite <-
+    requireRight
+      "finite point candidate projection"
+      (exactPointToEmbeddingCandidate exactFinite)
+  assertEqual "finite exact point roundtrip" finitePoint projectedFinite
+  assertEqual
+    "coincident exact segment endpoints"
+    (Left (ExactSegmentEndpointsCoincide exactFinite))
+    (exactSegment exactFinite exactFinite)
+  duplicateSegment <-
+    requireRight
+      "duplicate line intersection segment"
+      (exactSegment (integerPoint 0 0) (integerPoint 2 0))
+  assertEqual
+    "duplicate line intersection refusal"
+    (Left (ExactIntersectionNonUnique SegmentsDuplicate))
+    (exactLineIntersection duplicateSegment duplicateSegment)
+
+testOrdinaryDraftCertificate :: IO ()
+testOrdinaryDraftCertificate = do
+  half <- requireRight "ordinary draft half parameter" (exactRational 1 2)
+  let west = DraftVertexId 0
+      center = DraftVertexId 1
+      east = DraftVertexId 2
+      south = DraftVertexId 3
+      north = DraftVertexId 4
+      westCenter = DraftSegmentId 0
+      centerEast = DraftSegmentId 1
+      southCenter = DraftSegmentId 2
+      centerNorth = DraftSegmentId 3
+      draft =
+        ExactArrangementDraft
+          { draftVertices =
+              Map.fromList
+                [ (west, integerPoint (-1) 0)
+                , (center, integerPoint 0 0)
+                , (east, integerPoint 1 0)
+                , (south, integerPoint 0 (-1))
+                , (north, integerPoint 0 1)
+                ]
+          , draftSegments =
+              Map.fromList
+                [ (westCenter, (west, center))
+                , (centerEast, (center, east))
+                , (southCenter, (south, center))
+                , (centerNorth, (center, north))
+                ]
+          , draftSourceMemberships =
+              Map.fromList
+                [ ( DraftSourceId 0
+                  , [ (0, west)
+                    , (half, center)
+                    , (1, east)
+                    ]
+                  )
+                , ( DraftSourceId 1
+                  , [ (0, south)
+                    , (half, center)
+                    , (1, north)
+                    ]
+                  )
+                ]
+          , draftIncidences =
+              [ DraftIncidence westCenter southCenter SegmentsShareEndpoint
+              ]
+          , draftNeighborhoods =
+              [ DraftNeighborhood center (east :| [north, west, south])
+              ]
+          }
+  certificate <- requireRight "ordinary local embedding" (certifyLocalEmbedding draft)
+  assertEqual
+    "ordinary distinctness obligations"
+    10
+    (certificateRoundedVertexDistinctnessCount certificate)
+  assertEqual
+    "ordinary split-order obligations"
+    4
+    (certificateSplitOrderPreservationCount certificate)
+  assertEqual
+    "ordinary incidence obligations"
+    1
+    (certificateIncidenceRelationPreservationCount certificate)
+  assertEqual
+    "ordinary neighborhood obligations"
+    4
+    (certificateNeighborhoodOrientationStabilityCount certificate)
+  assertEqual
+    "ordinary residual"
+    (GlobalNoNewCrossing :| [])
+    (residualUndischargedObligations (certificateResidual certificate))
+  putStrLn
+    ( "local embedding certificate counts: "
+        <> show
+          ( certificateRoundedVertexDistinctnessCount certificate
+          , certificateSplitOrderPreservationCount certificate
+          , certificateIncidenceRelationPreservationCount certificate
+          , certificateNeighborhoodOrientationStabilityCount certificate
+          )
+    )
+  putStrLn
+    ( "local embedding declared residual: "
+        <> show (residualUndischargedObligations (certificateResidual certificate))
+    )
+
+testExactArithmeticReceipt :: IO ()
+testExactArithmeticReceipt = do
+  enabled <- getRTSStatsEnabled
+  unless enabled $
+    fail "exact arithmetic allocation receipt requires +RTS -T"
+  operands <-
+    traverse
+      ( \index ->
+          requireRight
+            "exact arithmetic receipt operand"
+            (exactRational (toInteger (index `mod` 89 + 1)) 97)
+      )
+      [0 .. 9_999 :: Int]
+  _ <- evaluate (force operands)
+  performGC
+  before <- allocated_bytes <$> getRTSStats
+  checksum <-
+    evaluate
+      ( force
+          ( foldl'
+              ( \accumulated value ->
+                  accumulated
+                    + fromEnum
+                      ( exactSignum
+                          ((value + 1) * value)
+                      )
+              )
+              0
+              operands
+          )
+      )
+  performGC
+  after <- allocated_bytes <$> getRTSStats
+  let operationCount = 3 * length operands
+      allocated = after - before
+  unless (checksum > 0) $
+    fail "exact arithmetic receipt did not force its operation chain"
+  putStrLn
+    ( "exact arithmetic receipt: operations="
+        <> show operationCount
+        <> " allocated-bytes="
+        <> show allocated
+        <> " checksum="
+        <> show checksum
+    )
+
+testEmbeddingAdmissionReceipt :: IO ()
+testEmbeddingAdmissionReceipt = do
+  let base = 2 ^ (52 :: Int)
+  firstCollision <- requireRight "admission receipt collision a" (exactRational (base * 4 + 1) 4)
+  secondCollision <- requireRight "admission receipt collision b" (exactRational (base * 4 + 2) 4)
+  let admittedDraft =
+        emptyDraft
+          { draftVertices =
+              Map.fromList
+                [ (DraftVertexId 0, integerPoint 0 0)
+                , (DraftVertexId 1, integerPoint 1 0)
+                ]
+          }
+      collisionDraft =
+        emptyDraft
+          { draftVertices =
+              Map.fromList
+                [ (DraftVertexId 0, exactPoint firstCollision 0)
+                , (DraftVertexId 1, exactPoint secondCollision 0)
+                ]
+          }
+      projectionDraft =
+        emptyDraft
+          { draftVertices =
+              Map.singleton
+                (DraftVertexId 0)
+                (exactPoint (fromInteger (10 ^ (400 :: Int))) 0)
+          }
+      outcomes =
+        map certifyLocalEmbedding [admittedDraft, collisionDraft, projectionDraft]
+      admitted = length [() | Right _ <- outcomes]
+      refused = length [() | Left _ <- outcomes]
+  assertEqual "embedding admission receipt admissions" 1 admitted
+  assertEqual "embedding admission receipt refusals" 2 refused
+  putStrLn
+    ( "embedding admission receipt: admitted="
+        <> show admitted
+        <> " refused="
+        <> show refused
+    )
+
+testFrozenBinary64RelationOracle :: IO ()
+testFrozenBinary64RelationOracle = do
+  let observed =
+        map
+          ( \(name, expected, (a, b, c, d)) ->
+              (name, expected, segmentRelation a b c d)
+          )
+          handFixtures
+      relations =
+        map (\(_, _, relation) -> relation) observed <> corpusRelations
+  traverse_
+    (\(name, expected, actual) -> assertEqual name expected actual)
+    observed
+  assertEqual "frozen relation count" 16_390 (length relations)
+  assertEqual
+    "frozen relation digest"
+    1_170_735_657_727_369_596
+    (digest relations)
+
+data ObservedRelation = ObservedRelation
+  { observedName :: !String
+  , observedExpectedRelation :: !SegmentRelation
+  , observedRoundedRelation :: !SegmentRelation
+  , observedExactRelation :: !SegmentRelation
+  }
+
+observeExactAndRounded
+  :: (String, SegmentRelation, (Point, Point, Point, Point))
+  -> IO ObservedRelation
+observeExactAndRounded (name, expected, points@(a, b, c, d)) = do
+  (exactA, exactB, exactC, exactD) <- exactPointTuple points
+  pure
+    ObservedRelation
+      { observedName = name
+      , observedExpectedRelation = expected
+      , observedRoundedRelation = segmentRelation a b c d
+      , observedExactRelation = exactSegmentRelation exactA exactB exactC exactD
+      }
+
+assertExpectedAndAgreement :: ObservedRelation -> IO ()
+assertExpectedAndAgreement observed = do
+  assertEqual
+    (observedName observed <> " binary64 expectation")
+    (observedExpectedRelation observed)
+    (observedRoundedRelation observed)
+  assertEqual
+    (observedName observed <> " exact agreement")
+    (observedRoundedRelation observed)
+    (observedExactRelation observed)
+
+contactFixtures
+  :: [(String, SegmentRelation, (Point, Point, Point, Point))]
+contactFixtures =
+  [ ( "endpoint-on-edge"
+    , SegmentEndpointTouchesInterior
+    , (Point 0 0, Point 2 0, Point 1 0, Point 1 1)
+    )
+  , ( "duplicate"
+    , SegmentsDuplicate
+    , (Point 0 0, Point 2 0, Point 2 0, Point 0 0)
+    )
+  , ( "partial-collinear-overlap"
+    , SegmentsCollinearlyOverlap
+    , (Point 0 0, Point 3 0, Point 1 0, Point 2 0)
+    )
+  ]
+
+assertContactAgreement
+  :: (String, SegmentRelation, (Point, Point, Point, Point))
+  -> IO ()
+assertContactAgreement fixture =
+  observeExactAndRounded fixture >>= assertExpectedAndAgreement
+
+exactPointTuple
+  :: (Point, Point, Point, Point)
+  -> IO (ExactPoint, ExactPoint, ExactPoint, ExactPoint)
+exactPointTuple (a, b, c, d) =
+  (,,,)
+    <$> requireRight "exact fixture point a" (exactPointFromPoint a)
+    <*> requireRight "exact fixture point b" (exactPointFromPoint b)
+    <*> requireRight "exact fixture point c" (exactPointFromPoint c)
+    <*> requireRight "exact fixture point d" (exactPointFromPoint d)
+
+exactCrossingAt :: ExactRational -> IO ExactPoint
+exactCrossingAt coordinate = do
+  horizontal <-
+    requireRight
+      "sub-ulp horizontal crossing segment"
+      ( exactSegment
+          (exactPoint (coordinate - 1) coordinate)
+          (exactPoint (coordinate + 1) coordinate)
+      )
+  vertical <-
+    requireRight
+      "sub-ulp vertical crossing segment"
+      ( exactSegment
+          (exactPoint coordinate (coordinate - 1))
+          (exactPoint coordinate (coordinate + 1))
+      )
+  requireRight "sub-ulp exact crossing" (exactLineIntersection horizontal vertical)
+
+assertRatio :: String -> Integer -> Integer -> ExactRational -> IO ()
+assertRatio label numerator denominator value = do
+  assertEqual (label <> " numerator") numerator (exactRationalNumerator value)
+  assertEqual (label <> " denominator") denominator (exactRationalDenominator value)
+
+emptyDraft :: ExactArrangementDraft
+emptyDraft =
+  ExactArrangementDraft
+    { draftVertices = Map.empty
+    , draftSegments = Map.empty
+    , draftSourceMemberships = Map.empty
+    , draftIncidences = []
+    , draftNeighborhoods = []
+    }
+
+assertLeftContains
+  :: String
+  -> (OverlayEmbeddingObstruction -> Bool)
+  -> Either (NonEmpty OverlayEmbeddingObstruction) value
+  -> IO ()
+assertLeftContains label predicate result =
+  case result of
+    Left obstructions ->
+      unless (any predicate obstructions) $
+        fail (label <> ": missing witness in " <> show obstructions)
+    Right _ -> fail (label <> ": unexpectedly certified")
+
+isRoundedCollision :: OverlayEmbeddingObstruction -> Bool
+isRoundedCollision RoundedVerticesCollide {} = True
+isRoundedCollision _ = False
+
+isProjectionRefusal :: OverlayEmbeddingObstruction -> Bool
+isProjectionRefusal VertexProjectionRefused {} = True
+isProjectionRefusal _ = False
+
+isIncidenceChange :: OverlayEmbeddingObstruction -> Bool
+isIncidenceChange IncidenceRelationChanged {} = True
+isIncidenceChange _ = False
+
+handFixtures :: [(String, SegmentRelation, (Point, Point, Point, Point))]
+handFixtures =
+  [ ("disjoint", SegmentsDisjoint, (Point 0 0, Point 1 0, Point 0 2, Point 1 2))
+  , ("duplicate", SegmentsDuplicate, (Point 0 0, Point 2 0, Point 2 0, Point 0 0))
+  , ("shared-endpoint", SegmentsShareEndpoint, (Point 0 0, Point 2 0, Point 2 0, Point 3 1))
+  , ("proper-crossing", SegmentsProperlyCross, (Point 0 0, Point 2 2, Point 0 2, Point 2 0))
+  , ("endpoint-interior", SegmentEndpointTouchesInterior, (Point 0 0, Point 2 0, Point 1 0, Point 1 1))
+  , ("collinear-overlap", SegmentsCollinearlyOverlap, (Point 0 0, Point 3 0, Point 1 0, Point 2 0))
+  ]
+
+corpusPoint :: Int -> Int -> Point
+corpusPoint index salt =
+  Point
+    (fromIntegral (((index * 17 + salt * 11) `mod` 47) - 23))
+    (fromIntegral (((index * 29 + salt * 7) `mod` 43) - 21))
+
+corpusRelations :: [SegmentRelation]
+corpusRelations =
+  [ segmentRelation
+      (corpusPoint index 1)
+      (corpusPoint index 2)
+      (corpusPoint index 3)
+      (corpusPoint index 4)
+  | index <- [0 .. 16_383]
+  ]
+
+digest :: [SegmentRelation] -> Word64
+digest = foldl' step 14_695_981_039_346_656_037
+ where
+  step :: Word64 -> SegmentRelation -> Word64
+  step hash relation =
+    (hash `xor` fromIntegral (fromEnum relation + 1)) * 1_099_511_628_211
diff --git a/test/native/Moonlight/Triangulation/NativeSpec.hs b/test/native/Moonlight/Triangulation/NativeSpec.hs
--- a/test/native/Moonlight/Triangulation/NativeSpec.hs
+++ b/test/native/Moonlight/Triangulation/NativeSpec.hs
@@ -7,13 +7,20 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 
 -- | The native core slice: everything that needs no serialization surface.
-module Moonlight.Triangulation.NativeSpec (tests) where
+module Moonlight.Triangulation.NativeSpec
+  ( tests
+  , regionMesh
+  , regionMeshFromPoints
+  , regionFaceSatisfies
+  ) where
 
 import Control.DeepSeq (NFData, force)
 import Control.Exception (evaluate)
 import Control.Monad (forM_, unless, void, when)
 import Control.Monad.ST (runST, stToIO)
 import GHC.Float (castDoubleToWord64)
+import Data.Foldable (toList, traverse_)
+import Data.List.NonEmpty (NonEmpty (..))
 import Data.Maybe (isJust)
 import qualified Data.Map.Strict as Map
 import qualified Data.Set as Set
@@ -102,6 +109,8 @@
   testConstrainedRefinement
   testCheckedLocalRefinement
   testRepeatedBoundaryAdjacentRefinement
+  testFaceComponentsAndBoundaries
+  testAlphaFaceFiltration
   testTraversal
   testRandomizedConstruction
   testErrors
@@ -776,6 +785,10 @@
   assertEqual "edge iterator" (numUndirectedEdges triangulation) (length (undirectedEdges triangulation))
   assertEqual "face iterator" (numInnerFaces triangulation) (length (innerFaces triangulation))
   assertEqual
+    "dense inner-face directed-edge projection"
+    (Just (V.toList (innerFaceDirectedEdgeTriples triangulation)))
+    (traverse (Dcel.innerFaceDirectedEdges triangulation) (innerFaces triangulation))
+  assertEqual
     "dense inner-face projection"
     (Just (V.toList (innerFaceVertexTriples triangulation)))
     (traverse (Dcel.innerFaceVertices triangulation) (innerFaces triangulation))
@@ -2198,7 +2211,7 @@
   seam <-
     requireRight
       "source-preserving separated constrained seam"
-      (joinSeparatedConstrainedWith Set.union left right)
+      (joinSeparatedConstrained left right)
   let joined = constrainedSeamResultTriangulation seam
       joinedFaceKeys = Set.fromList (fmap (faceKeyOf joined) (innerFaces joined))
       actualAnnotations =
@@ -2267,13 +2280,13 @@
   reversed <-
     requireRight
       "source-preserving reversed separated constrained seam"
-      (joinSeparatedConstrainedWith Set.union right left)
+      (joinSeparatedConstrained right left)
   assertEqual
     "reversing separated seam operands preserves the published constrained value"
     joined
     (constrainedSeamResultTriangulation reversed)
 
-  case joinSeparatedConstrainedWith Set.union left left of
+  case joinSeparatedConstrained left left of
     Left ConstraintUnionNotSeparated -> pure ()
     other -> fail ("non-separated constrained seam was not refused: " <> show other)
 
@@ -2652,6 +2665,353 @@
       points ->
         sum [x | Point x _ <- points] / fromIntegral (length points)
 
+testFaceComponentsAndBoundaries :: IO ()
+testFaceComponentsAndBoundaries = do
+  emptyBuild <- requirePointBuild "empty region components" []
+  collinearBuild <-
+    requirePointBuild
+      "collinear region components"
+      [Point 0 0, Point 1 0, Point 2 0]
+  assertEqual
+    "empty mesh has no face components"
+    ([] :: [(Bool, FaceComponent)])
+    (faceComponents (buildTriangulation emptyBuild) (const True))
+  assertEqual
+    "collinear mesh has no face components"
+    ([] :: [(Bool, FaceComponent)])
+    (faceComponents (buildTriangulation collinearBuild) (const True))
+
+  triangle <-
+    regionMeshFromPoints
+      "single triangle region"
+      [Point 0 0, Point 2 0, Point 0 2]
+  triangleBoundary <-
+    requireComponentBoundary "single triangle" True triangle (const True)
+  assertBoundaryShape "single triangle" triangle 3 [] triangleBoundary
+
+  square <-
+    regionMeshFromPoints
+      "uniform square region"
+      [Point 0 0, Point 2 0, Point 2 2, Point 0 2]
+  squareComponent <-
+    requireLabelledComponent
+      "uniform square component"
+      True
+      (faceComponents square (const True))
+  assertEqual
+    "component provenance mismatch is typed before DCEL lookup"
+    (Left (BoundaryComponentFaceOutOfRange (FaceId 2) 2))
+    (componentBoundary triangle squareComponent)
+  squareBoundary <- requireRight "uniform square boundary" (componentBoundary square squareComponent)
+  repeatedSquareBoundary <-
+    requireRight "repeated uniform square boundary" (componentBoundary square squareComponent)
+  assertEqual "boundary extraction is deterministic" squareBoundary repeatedSquareBoundary
+  assertEqual
+    "uniform square drops its Delaunay diagonal"
+    (Set.fromList [Point 0 0, Point 2 0, Point 2 2, Point 0 2])
+    (loopPointSet square (regionBoundaryOuterLoop squareBoundary))
+  assertBoundaryShape "uniform square" square 4 [] squareBoundary
+
+  let splitComponents =
+        faceComponents square (regionFaceSatisfies square (\point -> pointX point < 1))
+  assertEqual "split square component count" 2 (length splitComponents)
+  traverse_
+    (\(_, component) -> do
+       boundary <- requireRight "split square boundary" (componentBoundary square component)
+       assertBoundaryShape "split square component" square 3 [] boundary)
+    splitComponents
+
+  collinearHull <-
+    regionMeshFromPoints
+      "collinear hull simplification"
+      [ Point 0 0
+      , Point 1 0
+      , Point 2 0
+      , Point 2 2
+      , Point 0 2
+      , Point 1 1
+      ]
+  collinearBoundary <-
+    requireComponentBoundary "collinear hull" True collinearHull (const True)
+  assertEqual
+    "exact simplification drops a redundant hull site"
+    (Set.fromList [Point 0 0, Point 2 0, Point 2 2, Point 0 2])
+    (loopPointSet collinearHull (regionBoundaryOuterLoop collinearBoundary))
+
+  lShape <- regionMesh "concave L region" 2 2
+  let inL = regionFaceSatisfies lShape (\(Point x y) -> not (x > 1 && y > 1))
+  lBoundary <- requireComponentBoundary "concave L" True lShape inL
+  assertEqual
+    "concave L boundary"
+    ( Set.fromList
+        [ Point 0 0
+        , Point 2 0
+        , Point 2 1
+        , Point 1 1
+        , Point 1 2
+        , Point 0 2
+        ]
+    )
+    (loopPointSet lShape (regionBoundaryOuterLoop lBoundary))
+  assertBoundaryShape "concave L" lShape 6 [] lBoundary
+
+  annulus <- regionMesh "annulus region" 3 3
+  let outsideCenter =
+        regionFaceSatisfies annulus (\(Point x y) -> not (x > 1 && x < 2 && y > 1 && y < 2))
+  annulusBoundary <-
+    requireComponentBoundary "annulus outer" True annulus outsideCenter
+  assertBoundaryShape "annulus" annulus 4 [4] annulusBoundary
+
+  twoHoles <- regionMesh "ordered hole regions" 5 3
+  let outsideTwoCells =
+        regionFaceSatisfies twoHoles $ \(Point x y) ->
+          let cell = (floor x :: Int, floor y :: Int)
+           in cell /= (1, 1) && cell /= (3, 1)
+  twoHoleBoundary <-
+    requireComponentBoundary "two-hole outer" True twoHoles outsideTwoCells
+  assertBoundaryShape "two-hole" twoHoles 4 [4, 4] twoHoleBoundary
+
+  disconnected <- regionMesh "disconnected equal labels" 3 1
+  let outsideMiddle =
+        regionFaceSatisfies disconnected (\(Point x _) -> x < 1 || x > 2)
+      disconnectedComponents = faceComponents disconnected outsideMiddle
+      equalLabelComponents =
+        [component | (True, component) <- disconnectedComponents]
+  assertEqual "equal labels remain two disconnected components" 2 (length equalLabelComponents)
+  assertEqual
+    "face component order is deterministic"
+    disconnectedComponents
+    (faceComponents disconnected outsideMiddle)
+
+  pinched <- regionMesh "pinched region" 3 3
+  let pinchedSelection =
+        regionFaceSatisfies pinched $ \(Point x y) ->
+          let cell = (floor x :: Int, floor y :: Int)
+           in cell /= (0, 0) && cell /= (1, 1)
+  pinchedComponent <-
+    requireLabelledComponent
+      "pinched selected component"
+      True
+      (faceComponents pinched pinchedSelection)
+  case componentBoundary pinched pinchedComponent of
+    Left (BoundaryPinch vertex firstEdge secondEdge) -> do
+      assertEqual "pinch vertex" (Point 1 1) (vertexPoint pinched vertex)
+      when (firstEdge == secondEdge) $
+        fail ("pinch repeated one outgoing edge: " <> show firstEdge)
+    other -> fail ("pinched boundary produced " <> show other)
+
+testAlphaFaceFiltration :: IO ()
+testAlphaFaceFiltration = do
+  triangle <-
+    regionMeshFromPoints
+      "alpha right triangle"
+      [Point 0 0, Point 2 0, Point 0 2]
+  face <- case innerFaces triangle of
+    [singleFace] -> pure singleFace
+    faces -> fail ("alpha fixture faces: " <> show faces)
+  exactThreshold <- requireRight "exact alpha threshold" (mkRadiusSquared 2)
+  lowerThreshold <- requireRight "lower alpha threshold" (mkRadiusSquared 1.999)
+  zeroThreshold <- requireRight "zero alpha threshold" (mkRadiusSquared 0)
+  assertEqual "zero alpha threshold excludes face" False
+    (alphaShapeContainsFace zeroThreshold triangle face)
+  assertEqual "closed alpha threshold includes equality" True (alphaShapeContainsFace exactThreshold triangle face)
+  assertEqual "lower alpha threshold excludes face" False (alphaShapeContainsFace lowerThreshold triangle face)
+  assertEqual "outer face is absent from alpha filtration" False (alphaShapeContainsFace exactThreshold triangle outerFace)
+
+  let roundedPoints =
+        ( Point (-20) (-20)
+        , Point (-19) (-13)
+        , Point (-2) (-19)
+        )
+      (roundedFirst, roundedSecond, roundedThird) = roundedPoints
+      roundedRadiusSquared = 84.5
+  roundedTriangle <-
+    regionMeshFromPoints
+      "rounded alpha equality"
+      [roundedFirst, roundedSecond, roundedThird]
+  roundedFace <- case innerFaces roundedTriangle of
+    [singleFace] -> pure singleFace
+    faces -> fail ("rounded alpha fixture faces: " <> show faces)
+  roundedThreshold <-
+    requireRight
+      "rounded exact alpha threshold"
+      (mkRadiusSquared roundedRadiusSquared)
+  roundedLowerThreshold <-
+    requireRight
+      "rounded lower alpha threshold"
+      (mkRadiusSquared (roundedRadiusSquared - encodeFloat 1 (-46)))
+  assertEqual
+    "closed alpha equality is exact"
+    True
+    (alphaShapeContainsFace roundedThreshold roundedTriangle roundedFace)
+  assertEqual
+    "one binary64 step below exact alpha equality is excluded"
+    False
+    (alphaShapeContainsFace roundedLowerThreshold roundedTriangle roundedFace)
+
+  alphaBoundary <-
+    requireComponentBoundary
+      "alpha"
+      True
+      triangle
+      (alphaShapeContainsFace exactThreshold triangle)
+  assertBoundaryShape "alpha component" triangle 3 [] alphaBoundary
+
+  annulus <-
+    regionMeshFromPoints
+      "alpha annulus"
+      (ringPoints 4 32 <> ringPoints 2 16 <> ringPoints 3 24)
+  annulusThreshold <-
+    requireRight "alpha annulus threshold" (mkRadiusSquared 0.4)
+  annulusBoundary <-
+    requireComponentBoundary
+      "alpha annulus"
+      True
+      annulus
+      (alphaShapeContainsFace annulusThreshold annulus)
+  assertEqual
+    "alpha annulus hole count"
+    1
+    (length (regionBoundaryHoleLoops annulusBoundary))
+  assertLoopWinding
+    "alpha annulus outer winding"
+    GT
+    annulus
+    (regionBoundaryOuterLoop annulusBoundary)
+  traverse_
+    (assertLoopWinding "alpha annulus hole winding" LT annulus)
+    (regionBoundaryHoleLoops annulusBoundary)
+  traverse_
+    (\(label, value, failure) ->
+       assertEqual label (Left failure) (mkRadiusSquared value))
+    [ ("negative alpha threshold refusal", -1, NegativeRadiusSquared (-1))
+    , ("NaN alpha threshold refusal", 0 / 0, NonFiniteRadiusSquared ValueNaN)
+    , ("positive-infinite alpha threshold refusal", 1 / 0, NonFiniteRadiusSquared ValuePositiveInfinity)
+    , ("negative-infinite alpha threshold refusal", (-1) / 0, NonFiniteRadiusSquared ValueNegativeInfinity)
+    ]
+
+ringPoints :: Double -> Int -> [Point]
+ringPoints radius count =
+  [ Point (radius * cos angle) (radius * sin angle)
+  | index <- [0 .. count - 1]
+  , let angle = 2 * pi * fromIntegral index / fromIntegral count
+  ]
+
+regionMesh :: String -> Int -> Int -> IO NativeMesh
+regionMesh label widthInCells heightInCells =
+  regionMeshFromPoints
+    label
+    [ Point (fromIntegral x) (fromIntegral y)
+    | y <- [0 .. heightInCells]
+    , x <- [0 .. widthInCells]
+    ]
+
+regionMeshFromPoints :: String -> [Point] -> IO NativeMesh
+regionMeshFromPoints label points =
+  buildTriangulation <$> requirePointBuild label points
+
+regionFaceCentroid
+  :: Triangulation mode vertex directed undirected face
+  -> FaceId
+  -> Maybe Point
+regionFaceCentroid triangulation face =
+  (\(first, second, third) ->
+     centroid
+       (vertexPoint triangulation first)
+       (vertexPoint triangulation second)
+       (vertexPoint triangulation third))
+    <$> Dcel.innerFaceVertices triangulation face
+
+regionFaceSatisfies
+  :: Triangulation mode vertex directed undirected face
+  -> (Point -> Bool)
+  -> FaceId
+  -> Bool
+regionFaceSatisfies triangulation predicate =
+  maybe False predicate . regionFaceCentroid triangulation
+
+requireLabelledComponent
+  :: (Eq label, Show label)
+  => String
+  -> label
+  -> [(label, FaceComponent)]
+  -> IO FaceComponent
+requireLabelledComponent label expected components =
+  case [component | (actual, component) <- components, actual == expected] of
+    [component] -> pure component
+    matches ->
+      fail
+        ( label
+            <> ": expected one component for label "
+            <> show expected
+            <> ", got "
+            <> show (length matches)
+        )
+
+requireComponentBoundary
+  :: (Eq label, Show label)
+  => String
+  -> label
+  -> Triangulation mode vertex directed undirected face
+  -> (FaceId -> label)
+  -> IO RegionBoundary
+requireComponentBoundary label expected triangulation labelFace = do
+  component <-
+    requireLabelledComponent
+      (label <> " component")
+      expected
+      (faceComponents triangulation labelFace)
+  requireRight (label <> " boundary") (componentBoundary triangulation component)
+
+loopPointSet
+  :: Triangulation mode vertex directed undirected face
+  -> BoundaryLoop
+  -> Set.Set Point
+loopPointSet triangulation =
+  Set.fromList
+    . fmap (vertexPoint triangulation)
+    . toList
+    . boundaryLoopVertices
+
+assertLoopWinding
+  :: String
+  -> Ordering
+  -> Triangulation mode vertex directed undirected face
+  -> BoundaryLoop
+  -> IO ()
+assertLoopWinding label expected triangulation loop =
+  let first :| remaining = fmap (vertexPoint triangulation) (boundaryLoopVertices loop)
+      points = first : remaining
+      twiceArea =
+        sum
+          ( zipWith
+              (\(Point ax ay) (Point bx by) -> ax * by - ay * bx)
+              points
+              (remaining <> [first])
+          )
+   in assertEqual label expected (compare twiceArea 0)
+
+assertBoundaryShape
+  :: String
+  -> Triangulation mode vertex directed undirected face
+  -> Int
+  -> [Int]
+  -> RegionBoundary
+  -> IO ()
+assertBoundaryShape label triangulation outerVertexCount holeVertexCounts boundary = do
+  assertEqual (label <> " outer vertex count") outerVertexCount
+    (length (boundaryLoopVertices (regionBoundaryOuterLoop boundary)))
+  assertLoopWinding (label <> " outer winding") GT triangulation
+    (regionBoundaryOuterLoop boundary)
+  let holes = regionBoundaryHoleLoops boundary
+  assertEqual (label <> " hole count") (length holeVertexCounts) (length holes)
+  traverse_
+    (\(vertexCount, hole) -> do
+       assertEqual (label <> " hole vertex count") vertexCount
+         (length (boundaryLoopVertices hole))
+       assertLoopWinding (label <> " hole winding") LT triangulation hole)
+    (zip holeVertexCounts holes)
+
 testTraversal :: IO ()
 testTraversal = do
   built <- requirePointBuild "traversal" [Point (-3) 0, Point (-1) (-2), Point (-1) 2, Point 1 (-2), Point 1 2, Point 3 0]
@@ -2675,19 +3035,19 @@
   assertEqual "circle edge flood" bruteCircle circleEdges
   assertEqual
     "negative circle metric refusal"
-    (Left (NegativeRadiusSquared (-1)))
+    (Left (InvalidCircleRadius (NegativeRadiusSquared (-1))))
     (circleMetric (Point 0 0 :: Point) (-1))
   assertEqual
     "negative circle edge-query refusal"
-    (Left (NegativeRadiusSquared (-1)))
+    (Left (InvalidCircleRadius (NegativeRadiusSquared (-1))))
     (edgesInCircle triangulation (Point 0 0) (-1))
   assertEqual
     "negative circle vertex-query refusal"
-    (Left (NegativeRadiusSquared (-1)))
+    (Left (InvalidCircleRadius (NegativeRadiusSquared (-1))))
     (verticesInCircle triangulation (Point 0 0) (-1))
   assertEqual
     "NaN circle metric refusal"
-    (Left (NonFiniteRadiusSquared ValueNaN))
+    (Left (InvalidCircleRadius (NonFiniteRadiusSquared ValueNaN)))
     (circleMetric (Point 0 0 :: Point) (0 / 0))
   assertEqual
     "NaN circle center-x refusal"
@@ -2703,11 +3063,11 @@
     (verticesInCircle triangulation (Point ((-1) / 0) 0) 1)
   assertEqual
     "infinite circle edge-query refusal"
-    (Left (NonFiniteRadiusSquared ValuePositiveInfinity))
+    (Left (InvalidCircleRadius (NonFiniteRadiusSquared ValuePositiveInfinity)))
     (edgesInCircle triangulation (Point 0 0) (1 / 0))
   assertEqual
     "negative-infinite circle vertex-query refusal"
-    (Left (NonFiniteRadiusSquared ValueNegativeInfinity))
+    (Left (InvalidCircleRadius (NonFiniteRadiusSquared ValueNegativeInfinity)))
     (verticesInCircle triangulation (Point 0 0) ((-1) / 0))
   rectangleVertices <-
     Set.fromList
diff --git a/test/native/Moonlight/Triangulation/OverlaySpec.hs b/test/native/Moonlight/Triangulation/OverlaySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/native/Moonlight/Triangulation/OverlaySpec.hs
@@ -0,0 +1,776 @@
+-- | Focused common-refinement, provenance, selector, and grouped-publication
+-- acceptance.
+module Moonlight.Triangulation.OverlaySpec (tests) where
+
+import Control.Monad (foldM, unless, when)
+import Data.Foldable (traverse_)
+import qualified Data.Map.Strict as Map
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.Set as Set
+import qualified Data.Vector as V
+import Moonlight.Triangulation.CellSet
+  ( ExactCellSet
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , exactCellSetVertexCount
+  )
+import Moonlight.Triangulation.Dcel
+  ( faceData
+  , isConstraintEdge
+  , undirectedEdgeData
+  , undirectedEndpoints
+  , vertexData
+  )
+import Moonlight.Triangulation.Exact
+  ( ExactPoint
+  , ExactIntersectionError
+  , ExactSegment
+  , SegmentRelation (..)
+  , exactLineIntersection
+  , exactOnClosedSegment
+  , exactPointCoordinates
+  , exactSegment
+  , exactSegmentEndpoints
+  , exactSegmentRelation
+  )
+import Moonlight.Triangulation.Internal.ExactRational
+  ( exactRationalDenominator
+  )
+import Moonlight.Triangulation.Internal.ExactSegmentEvents
+  ( ExactSegmentEventPlan
+  , ExactSweepSegmentId (..)
+  , exactSegmentEventPlan
+  , exactSegmentRelationMap
+  , exactSegmentSplitPoints
+  , exactSegmentSweepMaximumHeight
+  )
+import Moonlight.Triangulation.Handles.Iterators.FixedIterators
+  ( innerFaces
+  , undirectedEdges
+  )
+import Moonlight.Triangulation.Internal.Overlay.Arrangement (certifyArrangement)
+import Moonlight.Triangulation.Internal.Overlay.Resident
+  ( OverlayDiagonalSchedule (..)
+  , residentOverlay
+  )
+import Moonlight.Triangulation.Overlay
+import Moonlight.Triangulation.Region
+import Support
+  ( assertEqual
+  , assertValid
+  , integerPoint
+  , rectangleComponent
+  , requireRight
+  )
+
+tests :: IO ()
+tests = do
+  testExactSegmentEventPlan
+  testOverlappingSquares
+  testLowerDimensionalIntersections
+  testSeamAndOverlapNormalization
+  testOutsidePairRemainsImplicit
+  testNestedAndNonDyadicOverlay
+  testSelectorRefusalsAndOperandSwap
+  testDisconnectedGroupedPublication
+  testMultiwayPointBoundaryCycles
+  testDiagonalScheduleIndependence
+
+testExactSegmentEventPlan :: IO ()
+testExactSegmentEventPlan = do
+  sourceFixture <-
+    traverse
+      (uncurry integerSegment)
+      [ ((0, 0), (4, 0))
+      , ((4, 0), (0, 0))
+      , ((2, 0), (6, 0))
+      , ((4, 0), (4, 4))
+      , ((1, -1), (1, 0))
+      , ((0, -1), (4, 1))
+      , ((10, 10), (11, 10))
+      ]
+  verticalAndMultiway <-
+    traverse
+      (uncurry integerSegment)
+      [ ((0, -4), (0, 4))
+      , ((-4, 0), (4, 0))
+      , ((-3, -3), (3, 3))
+      , ((-3, 3), (3, -3))
+      , ((0, 1), (0, 5))
+      , ((0, 4), (0, 7))
+      ]
+  grid <-
+    traverse
+      (uncurry integerSegment)
+      ( [((-1, y), (5, y)) | y <- [0 .. 4]]
+          <> [((x, -1), (x, 5)) | x <- [0 .. 4]]
+      )
+  collinearOverlaps <-
+    traverse
+      (uncurry integerSegment)
+      [ ((0, 0), (8, 0))
+      , ((1, 0), (3, 0))
+      , ((2, 0), (6, 0))
+      , ((5, 0), (9, 0))
+      , ((8, 0), (10, 0))
+      , ((11, 0), (12, 0))
+      ]
+  traverse_
+    compareEventPlanWithOracle
+    [sourceFixture, verticalAndMultiway, grid, collinearOverlaps]
+  let allRelations =
+        Set.fromList
+          [ exactSegmentRelation a b c d
+          | (leftIndex, left) <- zip [0 :: Int ..] sourceFixture
+          , right <- drop (leftIndex + 1) sourceFixture
+          , let (a, b) = exactSegmentEndpoints left
+                (c, d) = exactSegmentEndpoints right
+          ]
+  assertEqual
+    "source fixture covers every segment relation"
+    ( Set.fromList
+        [ SegmentsDisjoint
+        , SegmentsProperlyCross
+        , SegmentsShareEndpoint
+        , SegmentEndpointTouchesInterior
+        , SegmentsCollinearlyOverlap
+        , SegmentsDuplicate
+        ]
+    )
+    allRelations
+
+compareEventPlanWithOracle :: [ExactSegment] -> IO ()
+compareEventPlanWithOracle segments = do
+  let vector = V.fromList segments
+  plan <- requireRight "exact event sweep" (exactSegmentEventPlan vector)
+  expectedSplits <- requireRight "quadratic split oracle" (quadraticSplitPoints segments)
+  assertEqual
+    "sweep relation map agrees with quadratic oracle"
+    (quadraticRelationMap segments)
+    (exactSegmentRelationMap plan)
+  traverse_
+    (assertSegmentSplits plan expectedSplits)
+    [0 .. length segments - 1]
+  let heightLimit = 2 * ceilingLog2 (length segments + 1)
+  if exactSegmentSweepMaximumHeight plan <= heightLimit
+    then pure ()
+    else
+      fail
+        ( "AVL height exceeded conservative logarithmic bound: "
+            <> show (exactSegmentSweepMaximumHeight plan, heightLimit)
+        )
+
+assertSegmentSplits
+  :: ExactSegmentEventPlan
+  -> Map.Map Int (Set.Set ExactPoint)
+  -> Int
+  -> IO ()
+assertSegmentSplits plan expected segmentIndex =
+  assertEqual
+    ("sweep split points agree for segment " <> show segmentIndex)
+    (Map.findWithDefault Set.empty segmentIndex expected)
+    (Set.fromList (exactSegmentSplitPoints plan (ExactSweepSegmentId segmentIndex)))
+
+quadraticRelationMap
+  :: [ExactSegment]
+  -> Map.Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation
+quadraticRelationMap segments =
+  Map.fromList
+    [ ((ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex), relation)
+    | (leftIndex, left) <- zip [0 :: Int ..] segments
+    , (rightIndex, right) <- zip [leftIndex + 1 ..] (drop (leftIndex + 1) segments)
+    , let (a, b) = exactSegmentEndpoints left
+          (c, d) = exactSegmentEndpoints right
+          relation = exactSegmentRelation a b c d
+    , relation /= SegmentsDisjoint
+    ]
+
+quadraticSplitPoints
+  :: [ExactSegment]
+  -> Either ExactIntersectionError (Map.Map Int (Set.Set ExactPoint))
+quadraticSplitPoints segments =
+  foldM addRelation initial (segmentPairs segments)
+ where
+  initial =
+    Map.fromList
+      [ (index, Set.fromList [from, to])
+      | (index, segment) <- zip [0 :: Int ..] segments
+      , let (from, to) = exactSegmentEndpoints segment
+      ]
+  addRelation
+    :: Map.Map Int (Set.Set ExactPoint)
+    -> (Int, ExactSegment, Int, ExactSegment)
+    -> Either ExactIntersectionError (Map.Map Int (Set.Set ExactPoint))
+  addRelation splitPoints (leftIndex, left, rightIndex, right) = do
+    witnesses <- relationSplitWitnesses left right
+    pure
+      ( Map.insertWith Set.union rightIndex (Set.fromList witnesses)
+          (Map.insertWith Set.union leftIndex (Set.fromList witnesses) splitPoints)
+      )
+
+segmentPairs :: [value] -> [(Int, value, Int, value)]
+segmentPairs values =
+  [ (leftIndex, left, rightIndex, right)
+  | (leftIndex, left) <- zip [0 :: Int ..] values
+  , (rightIndex, right) <- zip [leftIndex + 1 ..] (drop (leftIndex + 1) values)
+  ]
+
+relationSplitWitnesses
+  :: ExactSegment
+  -> ExactSegment
+  -> Either ExactIntersectionError [ExactPoint]
+relationSplitWitnesses left right =
+  case exactSegmentRelation a b c d of
+    SegmentsDisjoint -> Right []
+    SegmentsProperlyCross -> (: []) <$> exactLineIntersection left right
+    SegmentsDuplicate -> Right []
+    _ ->
+      Right
+        ( Set.toAscList
+            ( Set.fromList
+                [ point
+                | point <- [a, b, c, d]
+                , exactOnClosedSegment a b point
+                , exactOnClosedSegment c d point
+                ]
+            )
+        )
+ where
+  (a, b) = exactSegmentEndpoints left
+  (c, d) = exactSegmentEndpoints right
+
+ceilingLog2 :: Int -> Int
+ceilingLog2 target = length (takeWhile (< target) (iterate (* 2) 1))
+
+integerSegment :: (Integer, Integer) -> (Integer, Integer) -> IO ExactSegment
+integerSegment (fromX, fromY) (toX, toY) =
+  requireRight
+    "integer exact segment"
+    (exactSegment (integerPoint fromX fromY) (integerPoint toX toY))
+
+testOverlappingSquares :: IO ()
+testOverlappingSquares = do
+  left <- singletonSquareLayer "left-outside" "left" 0 0 2 2
+  right <- singletonSquareLayer "right-outside" "right" 1 (-1) 3 1
+  result <- requireRight "overlapping square overlay" (overlayLayers left right)
+  assertOverlayIntegrity "overlapping square" result
+  let receipt = overlayReceipt result
+  assertEqual "overlay source segment count" 8 (overlayInputSegments receipt)
+  assertEqual "overlay has two proper crossings" 2 (overlayExactCrossings receipt)
+  when (overlayAtomicEdges receipt < 8) $
+    fail ("overlay lost atomic edges: " <> show receipt)
+  intersection <-
+    requireRight
+      "closed square intersection"
+      (overlayClosedIntersection (== "left") (== "right") result)
+  when
+    ( exactCellSetVertexCount intersection < 4
+        || exactCellSetEdgeCount intersection < 4
+        || exactCellSetFaceCount intersection < 1
+    )
+    (fail "closed intersection omitted incidence closure")
+  published <-
+    requireRight
+      "selected square intersection publication"
+      (overlaySelectedRegion (== ("left", "right")) result)
+  assertEqual
+    "selected square intersection component count"
+    1
+    (length (planarRegionComponents published))
+  assertEqual
+    "unbounded selected publication refusal"
+    (Left RegionUnboundedSelection)
+    (overlaySelectedRegion (const True) result)
+  merged <-
+    requireRight
+      "selected adjacent overlay cells merge"
+      (overlaySelectedRegion (\(leftLabel, _) -> leftLabel == "left") result)
+  assertEqual
+    "selected adjacent overlay cells publish one component"
+    [4]
+    (map (length . exactLoopPoints . polygonOuterLoop) (planarRegionComponents merged))
+
+testLowerDimensionalIntersections :: IO ()
+testLowerDimensionalIntersections = do
+  left <- singletonSquareLayer "left-outside" "left" 0 0 1 1
+  pointTouching <- singletonSquareLayer "right-outside" "right" 1 1 2 2
+  pointResult <- requireRight "point-touching overlay" (overlayLayers left pointTouching)
+  assertOverlayIntegrity "point-touching" pointResult
+  assertEqual
+    "point-touching unbounded cell keeps two simple boundary cycles"
+    (Just 2)
+    (unboundedLoopCount pointResult)
+  pointIntersection <-
+    requireRight
+      "point-only closed intersection"
+      (overlayClosedIntersection (== "left") (== "right") pointResult)
+  assertEqual
+    "point-only closed intersection retains only its zero-cell"
+    (1, 0, 0)
+    ( exactCellSetVertexCount pointIntersection
+    , exactCellSetEdgeCount pointIntersection
+    , exactCellSetFaceCount pointIntersection
+    )
+
+  edgeTouching <- singletonSquareLayer "right-outside" "right" 1 0 2 1
+  edgeResult <- requireRight "edge-touching overlay" (overlayLayers left edgeTouching)
+  assertOverlayIntegrity "edge-touching" edgeResult
+  edgeIntersection <-
+    requireRight
+      "edge-only closed intersection"
+      (overlayClosedIntersection (== "left") (== "right") edgeResult)
+  assertEqual
+    "edge-only closed intersection retains its closure"
+    (2, 1, 0)
+    ( exactCellSetVertexCount edgeIntersection
+    , exactCellSetEdgeCount edgeIntersection
+    , exactCellSetFaceCount edgeIntersection
+    )
+  publishedEdgeOnly <-
+    requireRight
+      "edge-only polygon publication"
+      (overlaySelectedRegion (== ("left", "right")) edgeResult)
+  assertEqual
+    "edge-only selection is not fabricated into a polygon"
+    0
+    (length (planarRegionComponents publishedEdgeOnly))
+
+testSeamAndOverlapNormalization :: IO ()
+testSeamAndOverlapNormalization = do
+  first <- rectangleComponent 0 0 1 1
+  second <- rectangleComponent 1 0 2 1
+  joinedRegion <- requireRight "same-label joined region" (planarRegion [first, second])
+  joinedLayer <-
+    requireRight
+      "same-label joined layer"
+      (planarLayer "outside" (Map.singleton "inside" joinedRegion))
+  emptyLayer <- requireRight "empty normalization layer" (planarLayer "void" Map.empty)
+  seamResult <- requireRight "same-label seam overlay" (overlayLayers joinedLayer emptyLayer)
+  assertOverlayIntegrity "same-label seam" seamResult
+  assertEqual "joined rectangle has one unbounded boundary cycle" (Just 1) (unboundedLoopCount seamResult)
+  assertEqual
+    "same-label shared boundary is removed before topology"
+    6
+    (overlayAtomicEdges (overlayReceipt seamResult))
+  let seamPublication = overlayPlanarLayer seamResult
+  assertEqual
+    "the implicit outside pair is never duplicated as a bounded layer key"
+    Nothing
+    ( Map.lookup
+        ("outside", "void")
+        (planarLayerRegions seamPublication)
+    )
+  assertEqual
+    "same-label seam dissolves to the rectangle boundary"
+    [4]
+    ( map
+        (length . exactLoopPoints . polygonOuterLoop)
+        ( maybe
+            []
+            planarRegionComponents
+            (Map.lookup ("inside", "void") (planarLayerRegions seamPublication))
+        )
+    )
+
+  duplicateLeft <- singletonSquareLayer "left-outside" "left" 0 0 2 2
+  duplicateRight <- singletonSquareLayer "right-outside" "right" 0 0 2 2
+  duplicateResult <-
+    requireRight "cross-operand duplicate boundary overlay" (overlayLayers duplicateLeft duplicateRight)
+  assertOverlayIntegrity "duplicate boundary" duplicateResult
+  assertEqual
+    "duplicate boundaries normalize to one atomic cycle"
+    4
+    (overlayAtomicEdges (overlayReceipt duplicateResult))
+
+  partialLeft <- singletonSquareLayer "left-outside" "left" 0 0 3 2
+  partialRight <- singletonSquareLayer "right-outside" "right" 1 0 4 1
+  partialResult <-
+    requireRight "partial collinear overlap" (overlayLayers partialLeft partialRight)
+  assertOverlayIntegrity "partial collinear overlap" partialResult
+  if overlayOverlapIntervals (overlayReceipt partialResult) > 0
+    then pure ()
+    else fail "partial collinear overlap emitted no overlap event"
+
+testOutsidePairRemainsImplicit :: IO ()
+testOutsidePairRemainsImplicit = do
+  outer <-
+    requireRight
+      "annulus outer loop"
+      ( exactLoop
+          ( integerPoint 0 0
+              :| [integerPoint 4 0, integerPoint 4 4, integerPoint 0 4]
+          )
+      )
+  hole <-
+    requireRight
+      "annulus hole loop"
+      ( exactLoop
+          ( integerPoint 1 1
+              :| [integerPoint 1 3, integerPoint 3 3, integerPoint 3 1]
+          )
+      )
+  component <- requireRight "annulus component" (polygonComponent outer [hole])
+  region <- requireRight "annulus region" (planarRegion [component])
+  left <-
+    requireRight
+      "annulus layer"
+      (planarLayer "outside" (Map.singleton "annulus" region))
+  right <- requireRight "annulus empty layer" (planarLayer "void" Map.empty)
+  result <- requireRight "annulus overlay" (overlayLayers left right)
+  let published = overlayPlanarLayer result
+  assertEqual
+    "bounded cavities remain represented by the implicit outside pair"
+    Nothing
+    ( Map.lookup
+        ("outside", "void")
+        (planarLayerRegions published)
+    )
+
+testSelectorRefusalsAndOperandSwap :: IO ()
+testSelectorRefusalsAndOperandSwap = do
+  left <- singletonSquareLayer "left-outside" "left" 0 0 2 2
+  right <- singletonSquareLayer "right-outside" "right" 1 (-1) 3 1
+  result <- requireRight "selector refusal overlay" (overlayLayers left right)
+  assertSelectionRefusal
+    "closed union refuses selected outside cell"
+    (OverlaySelectionContainsUnboundedCell ClosedUnionSelection)
+    (overlayClosedUnion (== "left-outside") (const False) result)
+  assertSelectionRefusal
+    "closed intersection refuses selected outside cell"
+    (OverlaySelectionContainsUnboundedCell ClosedIntersectionSelection)
+    ( overlayClosedIntersection
+        (== "left-outside")
+        (== "right-outside")
+        result
+    )
+  assertSelectionRefusal
+    "regularized difference refuses selected outside cell"
+    (OverlaySelectionContainsUnboundedCell RegularizedDifferenceSelection)
+    ( overlayRegularizedDifference
+        (== "left-outside")
+        (== "right")
+        result
+    )
+
+  swapped <- requireRight "operand-swapped overlay" (overlayLayers right left)
+  assertOverlayIntegrity "operand-swapped" swapped
+  assertEqual
+    "operand swap preserves exact arrangement vertices"
+    (Set.fromList (map (overlayExactPoint . snd) (overlayArrangementVertices result)))
+    (Set.fromList (map (overlayExactPoint . snd) (overlayArrangementVertices swapped)))
+  assertEqual
+    "operand swap preserves cells and exchanges labels"
+    [ (overlayCellGeometry cell, (overlayCellRight cell, overlayCellLeft cell))
+    | (_, cell) <- overlayCells result
+    ]
+    [ (overlayCellGeometry cell, (overlayCellLeft cell, overlayCellRight cell))
+    | (_, cell) <- overlayCells swapped
+    ]
+  assertEqual
+    "operand swap exchanges typed vertex provenance"
+    (vertexOriginCensus True result)
+    (vertexOriginCensus False swapped)
+
+testNestedAndNonDyadicOverlay :: IO ()
+testNestedAndNonDyadicOverlay = do
+  outer <- singletonSquareLayer "left-outside" "left" 0 0 4 4
+  inner <- singletonSquareLayer "right-outside" "right" 1 1 3 3
+  nested <- requireRight "nested overlay" (overlayLayers outer inner)
+  assertOverlayIntegrity "nested" nested
+  assertEqual "nested overlay unbounded boundary is outermost only" (Just 1) (unboundedLoopCount nested)
+  assertEqual
+    "nested overlay has unbounded, shell, and intersection cells"
+    3
+    (length (overlayCells nested))
+
+  leftTriangle <-
+    triangleLayer
+      "left-outside"
+      "left"
+      ((0, 0), (4, 0), (0, 4))
+  rightTriangle <-
+    triangleLayer
+      "right-outside"
+      "right"
+      ((1, -1), (3, -1), (2, 2))
+  nonDyadic <-
+    requireRight "non-dyadic proper-crossing overlay" (overlayLayers leftTriangle rightTriangle)
+  assertOverlayIntegrity "non-dyadic proper crossing" nonDyadic
+  let hasNonDyadicCoordinate =
+        any
+          (\(_, vertex) ->
+             let (x, y) = exactPointCoordinates (overlayExactPoint vertex)
+              in any
+                   (not . isPowerOfTwo . exactRationalDenominator)
+                   [x, y])
+          (overlayArrangementVertices nonDyadic)
+  if overlayExactCrossings (overlayReceipt nonDyadic) > 0 && hasNonDyadicCoordinate
+    then pure ()
+    else fail "proper-crossing overlay lost its non-dyadic exact witness"
+
+assertSelectionRefusal
+  :: String
+  -> OverlaySelectionError
+  -> Either OverlaySelectionError ExactCellSet
+  -> IO ()
+assertSelectionRefusal label expected actual =
+  case actual of
+    Left obstruction -> assertEqual label expected obstruction
+    Right _ -> fail (label <> ": unbounded selection was truncated")
+
+testDisconnectedGroupedPublication :: IO ()
+testDisconnectedGroupedPublication = do
+  first <- rectangleComponent 0 0 1 1
+  second <- rectangleComponent 3 0 4 1
+  region <- requireRight "two-island region" (planarRegion [first, second])
+  left <-
+    requireRight
+      "two-island layer"
+      (planarLayer "outside" (Map.singleton "island" region))
+  right <- requireRight "empty right layer" (planarLayer "void" Map.empty)
+  result <- requireRight "two-island overlay" (overlayLayers left right)
+  assertEqual "two islands give two unbounded boundary cycles" (Just 2) (unboundedLoopCount result)
+  let published = overlayPlanarLayer result
+  let components =
+        maybe
+          []
+          planarRegionComponents
+          (Map.lookup ("island", "void") (planarLayerRegions published))
+  assertEqual "equal labels group after component descent" 2 (length components)
+  reversedRegion <- requireRight "reversed two-island region" (planarRegion [second, first])
+  reversedLeft <-
+    requireRight
+      "reversed two-island layer"
+      (planarLayer "outside" (Map.singleton "island" reversedRegion))
+  reversedResult <- requireRight "reversed two-island overlay" (overlayLayers reversedLeft right)
+  assertEqual
+    "component construction order cannot perturb stable cells"
+    (overlayCells result)
+    (overlayCells reversedResult)
+
+testMultiwayPointBoundaryCycles :: IO ()
+testMultiwayPointBoundaryCycles = do
+  components <-
+    traverse
+      triangleComponent
+      [ ((0, 0), (2, 0), (1, 1))
+      , ((0, 0), (-1, 1), (-2, 0))
+      , ((0, 0), (-1, -1), (1, -1))
+      ]
+  region <- requireRight "three point-touching components" (planarRegion components)
+  left <-
+    requireRight
+      "three point-touching layer"
+      (planarLayer "outside" (Map.singleton "inside" region))
+  right <- requireRight "empty multiway right layer" (planarLayer "void" Map.empty)
+  result <- requireRight "three-way point-touching overlay" (overlayLayers left right)
+  assertOverlayIntegrity "three-way point-touching" result
+  assertEqual
+    "multiway point contact descends to three simple unbounded cycles"
+    (Just 3)
+    (unboundedLoopCount result)
+
+testDiagonalScheduleIndependence :: IO ()
+testDiagonalScheduleIndependence = do
+  left <- singletonSquareLayer "outside" "inside" 0 0 2 2
+  right <- requireRight "empty diagonal-policy layer" (planarLayer "void" Map.empty)
+  certified <-
+    requireRight
+      "diagonal-policy arrangement certification"
+      (certifyArrangement left right)
+  canonical <-
+    requireRight
+      "canonical resident diagonal schedule"
+      (residentOverlay CanonicalOverlayDiagonals ("outside", "void") certified)
+  alternate <-
+    requireRight
+      "alternate resident diagonal schedule"
+      (residentOverlay FlipFirstAdmissibleDiagonal ("outside", "void") certified)
+  assertOverlayIntegrity "canonical diagonal schedule" canonical
+  assertOverlayIntegrity "alternate diagonal schedule" alternate
+  assertEqual
+    "exact cells are independent of resident diagonal policy"
+    (overlayCells canonical)
+    (overlayCells alternate)
+  when (residentDiagonalKeys canonical == residentDiagonalKeys alternate) $
+    fail "alternate diagonal fixture did not change the resident topology"
+
+residentDiagonalKeys
+  :: OverlayResult leftLabel rightLabel
+  -> Set.Set (ExactPoint, ExactPoint)
+residentDiagonalKeys result =
+  Set.fromList
+    [ if fromPoint <= toPoint
+        then (fromPoint, toPoint)
+        else (toPoint, fromPoint)
+    | edge <- undirectedEdges triangulation
+    , OverlayDiagonal <- [undirectedEdgeData triangulation edge]
+    , let (fromVertex, toVertex) = undirectedEndpoints triangulation edge
+          fromPoint = overlayExactPoint (vertexData triangulation fromVertex)
+          toPoint = overlayExactPoint (vertexData triangulation toVertex)
+    ]
+ where
+  triangulation = overlayEmbeddedTriangulation result
+
+assertOverlayIntegrity
+  :: String
+  -> OverlayResult leftLabel rightLabel
+  -> IO ()
+assertOverlayIntegrity label result = do
+  let triangulation = overlayEmbeddedTriangulation result
+      boundaryEdges = overlayArrangementEdges result
+      cellIds = Set.fromList (map fst (overlayCells result))
+      residentFaceIds =
+        Set.fromList
+          [ overlayFaceCellId (faceData triangulation face)
+          | face <- innerFaces triangulation
+          ]
+  assertValid (label <> " embedded triangulation") triangulation
+  assertEqual
+    (label <> " atomic constraint correspondence")
+    (overlayAtomicEdges (overlayReceipt result))
+    (length boundaryEdges)
+  unless (all (isConstraintEdge triangulation . fst) boundaryEdges) $
+    fail (label <> ": boundary payload names an unconstrained edge")
+  unless (residentFaceIds `Set.isSubsetOf` cellIds) $
+    fail (label <> ": resident face names no exact cell")
+  unless
+    ( all
+        (vertexOriginIsNonEmpty . overlayVertexOrigin . snd)
+        (overlayArrangementVertices result)
+    )
+    (fail (label <> ": exact vertex lost all typed origins"))
+  unless (all (edgeOriginIsNonEmpty . snd) boundaryEdges) $
+    fail (label <> ": atomic edge lost all typed origins")
+  atomicSegments <-
+    traverse
+      (\(edge, _) ->
+         let (fromVertex, toVertex) = undirectedEndpoints triangulation edge
+          in requireRight
+               (label <> " atomic segment")
+               ( exactSegment
+                   (overlayExactPoint (vertexData triangulation fromVertex))
+                   (overlayExactPoint (vertexData triangulation toVertex))
+               ))
+      boundaryEdges
+  atomicPlan <-
+    requireRight
+      (label <> " atomic endpoint-incidence proof")
+      (exactSegmentEventPlan (V.fromList atomicSegments))
+  assertEqual
+    (label <> " atomics have only endpoint-incidence relations")
+    (endpointIncidenceRelations atomicSegments)
+    (exactSegmentRelationMap atomicPlan)
+
+endpointIncidenceRelations
+  :: [ExactSegment]
+  -> Map.Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation
+endpointIncidenceRelations segments =
+  Map.fromList
+    [ ( (ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex)
+      , SegmentsShareEndpoint
+      )
+    | (leftIndex, left) <- zip [0 :: Int ..] segments
+    , (rightIndex, right) <- zip [leftIndex + 1 ..] (drop (leftIndex + 1) segments)
+    , let (leftFrom, leftTo) = exactSegmentEndpoints left
+          (rightFrom, rightTo) = exactSegmentEndpoints right
+    , not
+        ( Set.null
+            ( Set.intersection
+                (Set.fromList [leftFrom, leftTo])
+                (Set.fromList [rightFrom, rightTo])
+            )
+        )
+    ]
+
+vertexOriginIsNonEmpty :: OverlayVertexOrigin -> Bool
+vertexOriginIsNonEmpty origin =
+  not
+    ( null (overlayOriginLeftVertices origin)
+        && null (overlayOriginRightVertices origin)
+        && null (overlayOriginLeftEdges origin)
+        && null (overlayOriginRightEdges origin)
+    )
+
+edgeOriginIsNonEmpty :: OverlayEdgeOrigin -> Bool
+edgeOriginIsNonEmpty origin =
+  not
+    (null (overlayEdgeLeftSources origin) && null (overlayEdgeRightSources origin))
+
+vertexOriginCensus
+  :: Bool
+  -> OverlayResult String String
+  -> Map.Map ExactPoint (Int, Int, Int, Int)
+vertexOriginCensus preserveSides result =
+  Map.fromList
+    [ ( overlayExactPoint vertex
+      , if preserveSides
+          then census origin
+          else swapCensus (census origin)
+      )
+    | (_, vertex) <- overlayArrangementVertices result
+    , let origin = overlayVertexOrigin vertex
+    ]
+ where
+  census :: OverlayVertexOrigin -> (Int, Int, Int, Int)
+  census origin =
+    ( length (overlayOriginLeftVertices origin)
+    , length (overlayOriginRightVertices origin)
+    , length (overlayOriginLeftEdges origin)
+    , length (overlayOriginRightEdges origin)
+    )
+  swapCensus :: (Int, Int, Int, Int) -> (Int, Int, Int, Int)
+  swapCensus (leftVertices, rightVertices, leftEdges, rightEdges) =
+    (rightVertices, leftVertices, rightEdges, leftEdges)
+
+unboundedLoopCount :: OverlayResult leftLabel rightLabel -> Maybe Int
+unboundedLoopCount result =
+  case
+    [ length loops
+    | (cellId, cell) <- overlayCells result
+    , cellId == OverlayCellId 0
+    , UnboundedOverlayCell loops <- [overlayCellGeometry cell]
+    ] of
+    [count] -> Just count
+    _ -> Nothing
+
+singletonSquareLayer
+  :: String
+  -> String
+  -> Integer
+  -> Integer
+  -> Integer
+  -> Integer
+  -> IO (PlanarLayer String)
+singletonSquareLayer outside inside minX minY maxX maxY = do
+  component <- rectangleComponent minX minY maxX maxY
+  region <- requireRight "singleton square region" (planarRegion [component])
+  requireRight "singleton square layer" (planarLayer outside (Map.singleton inside region))
+
+triangleLayer
+  :: String
+  -> String
+  -> ((Integer, Integer), (Integer, Integer), (Integer, Integer))
+  -> IO (PlanarLayer String)
+triangleLayer outside inside (firstPoint, secondPoint, thirdPoint) = do
+  component <- triangleComponent (firstPoint, secondPoint, thirdPoint)
+  region <- requireRight "overlay triangle region" (planarRegion [component])
+  requireRight "overlay triangle layer" (planarLayer outside (Map.singleton inside region))
+
+triangleComponent
+  :: ((Integer, Integer), (Integer, Integer), (Integer, Integer))
+  -> IO PolygonComponent
+triangleComponent (firstPoint, secondPoint, thirdPoint) = do
+  loop <-
+    requireRight
+      "overlay triangle loop"
+      ( exactLoop
+          ( uncurry integerPoint firstPoint
+              :| [uncurry integerPoint secondPoint, uncurry integerPoint thirdPoint]
+          )
+      )
+  requireRight "overlay triangle component" (polygonComponent loop [])
+
+isPowerOfTwo :: Integer -> Bool
+isPowerOfTwo value =
+  value > 0 && value `elem` takeWhile (<= value) (iterate (* 2) 1)
diff --git a/test/native/Moonlight/Triangulation/RegionSpec.hs b/test/native/Moonlight/Triangulation/RegionSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/native/Moonlight/Triangulation/RegionSpec.hs
@@ -0,0 +1,238 @@
+-- | Focused exact-region authoring and ordinary publication acceptance.
+module Moonlight.Triangulation.RegionSpec (tests) where
+
+import qualified Data.Map.Strict as Map
+import qualified Data.Vector as V
+import Moonlight.Triangulation
+  ( Point (..)
+  , buildTriangulation
+  , delaunay
+  , unitElementDefaults
+  )
+import Moonlight.Triangulation.CellSet
+  ( CellSelectionError (..)
+  , closeFaceCellSet
+  , exactCellSet
+  , exactCellSetEdgeCount
+  , exactCellSetFaceCount
+  , exactCellSetVertexCount
+  )
+import Moonlight.Triangulation.Dcel (outerFace)
+import Moonlight.Triangulation.Handles.Iterators.FixedIterators
+  ( innerFaces
+  , undirectedEdges
+  )
+import Moonlight.Triangulation.Internal.Region.Publication
+  ( labelledPlanarLayerFromExactCoordinates
+  )
+import Moonlight.Triangulation.FloodFillIterator (BoundaryObstruction (BoundaryPinch))
+import Moonlight.Triangulation.NativeSpec
+  ( regionFaceSatisfies
+  , regionMesh
+  , regionMeshFromPoints
+  )
+import Moonlight.Triangulation.Region
+import Support (assertEqual, integerPoint, rectangleComponent, requireRight)
+
+tests :: IO ()
+tests = do
+  testExactAuthoring
+  testOrdinaryPublication
+  testExactCellSetAdmission
+  testGroupedPublicationFixtures
+  testPinchPublicationRefusal
+
+testExactAuthoring :: IO ()
+testExactAuthoring = do
+  firstComponent <- rectangleComponent 0 0 2 2
+  secondComponent <- rectangleComponent 4 0 5 1
+  region <- requireRight "disconnected exact region" (planarRegion [firstComponent, secondComponent])
+  layer <-
+    requireRight
+      "disconnected labelled layer"
+      (planarLayer "outside" (Map.singleton "land" region))
+  assertEqual
+    "disconnected exact components remain separate"
+    2
+    ( maybe
+        0
+        (length . planarRegionComponents)
+        (Map.lookup "land" (planarLayerRegions layer))
+    )
+  assertEqual
+    "interior exact point location"
+    RegionInterior
+    (regionPointLocation region (integerPoint 1 1))
+  assertEqual
+    "exterior exact point location"
+    RegionExterior
+    (regionPointLocation region (integerPoint 3 1))
+
+testOrdinaryPublication :: IO ()
+testOrdinaryPublication = do
+  built <-
+    requireRight
+      "ordinary square triangulation"
+      ( delaunay
+          unitElementDefaults
+          (V.fromList [Point 0 0, Point 2 0, Point 2 2, Point 0 2])
+      )
+  layer <-
+    requireRight
+      "ordinary square labelled publication"
+      (labelledPlanarLayer "outside" (buildTriangulation built) (const "inside"))
+  case
+    labelledPlanarLayerFromExactCoordinates
+      "outside"
+      (buildTriangulation built)
+      (\vertex -> Left (RegionCoordinateMissing vertex))
+      (const (Right "inside")) of
+    Left (RegionCoordinateMissing _) -> pure ()
+    other -> fail ("missing exact publication coordinate produced " <> show other)
+  assertEqual
+    "ordinary publication omits outside label"
+    ["inside"]
+    (Map.keys (planarLayerRegions layer))
+  let components =
+        maybe [] planarRegionComponents (Map.lookup "inside" (planarLayerRegions layer))
+  assertEqual "ordinary square component count" 1 (length components)
+  assertEqual
+    "ordinary square drops resident diagonal"
+    [4]
+    (map (length . exactLoopPoints . polygonOuterLoop) components)
+
+testExactCellSetAdmission :: IO ()
+testExactCellSetAdmission = do
+  built <-
+    requireRight
+      "cell-set triangle"
+      (delaunay unitElementDefaults (V.fromList [Point 0 0, Point 2 0, Point 0 2]))
+  let triangulation = buildTriangulation built
+  face <-
+    case innerFaces triangulation of
+      [singleFace] -> pure singleFace
+      faces -> fail ("cell-set triangle faces: " <> show faces)
+  closed <- requireRight "closed face cell set" (closeFaceCellSet triangulation [face])
+  assertEqual
+    "face cell set carries its complete downward closure"
+    (3, 3, 1)
+    ( exactCellSetVertexCount closed
+    , exactCellSetEdgeCount closed
+    , exactCellSetFaceCount closed
+    )
+  case exactCellSet triangulation [] [] [outerFace] of
+    Left CellOuterFaceSelected -> pure ()
+    _ -> fail "cell set admitted the unbounded outer face"
+  edge <-
+    case undirectedEdges triangulation of
+      firstEdge : _ -> pure firstEdge
+      [] -> fail "cell-set triangle has no edge"
+  case exactCellSet triangulation [] [edge] [] of
+    Left (CellEdgeBoundaryMissing failedEdge _) ->
+      assertEqual "edge closure witness" edge failedEdge
+    _ -> fail "cell set admitted an edge without its boundary vertices"
+
+testGroupedPublicationFixtures :: IO ()
+testGroupedPublicationFixtures = do
+  triangle <-
+    regionMeshFromPoints
+      "published single triangle"
+      [Point 0 0, Point 2 0, Point 0 2]
+  triangleLayer <-
+    requireRight
+      "published single triangle layer"
+      (labelledPlanarLayer (0 :: Int) triangle (const 1))
+  assertComponentShape "published single triangle" 1 3 [] triangleLayer
+
+  concave <- regionMesh "published concave L" 2 2
+  let concaveLabel =
+        regionFaceSatisfies concave (\(Point x y) -> not (x > 1 && y > 1))
+  concaveLayer <-
+    requireRight
+      "published concave L layer"
+      (labelledPlanarLayer False concave concaveLabel)
+  assertComponentShape "published concave L" True 6 [] concaveLayer
+
+  annulus <- regionMesh "published annulus" 3 3
+  let annulusLabel =
+        regionFaceSatisfies annulus
+          (\(Point x y) -> not (x > 1 && x < 2 && y > 1 && y < 2))
+  annulusLayer <-
+    requireRight
+      "published annulus layer"
+      (labelledPlanarLayer False annulus annulusLabel)
+  assertComponentShape "published annulus" True 4 [4] annulusLayer
+
+  twoHoles <- regionMesh "published two holes" 5 3
+  let twoHoleLabel =
+        regionFaceSatisfies twoHoles $ \(Point x y) ->
+          let cell = (floor x :: Int, floor y :: Int)
+           in cell /= (1, 1) && cell /= (3, 1)
+  twoHoleLayer <-
+    requireRight
+      "published two-hole layer"
+      (labelledPlanarLayer False twoHoles twoHoleLabel)
+  assertComponentShape "published two holes" True 4 [4, 4] twoHoleLayer
+
+  disconnected <- regionMesh "published disconnected islands" 3 1
+  let islandLabel =
+        regionFaceSatisfies disconnected (\(Point x _) -> x < 1 || x > 2)
+  disconnectedLayer <-
+    requireRight
+      "published disconnected layer"
+      (labelledPlanarLayer False disconnected islandLabel)
+  assertEqual
+    "published equal label keeps disconnected components"
+    2
+    (length (componentsFor True disconnectedLayer))
+
+  islandInHole <- regionMesh "published island in hole" 3 3
+  let islandInHoleLabel face =
+        if regionFaceSatisfies islandInHole
+             (\(Point x y) -> x > 1 && x < 2 && y > 1 && y < 2)
+             face
+          then (2 :: Int)
+          else 1
+  nestedLayer <-
+    requireRight
+      "published island-in-hole layer"
+      (labelledPlanarLayer 0 islandInHole islandInHoleLabel)
+  assertComponentShape "published shell around island" 1 4 [4] nestedLayer
+  assertComponentShape "published island inside hole" 2 4 [] nestedLayer
+
+testPinchPublicationRefusal :: IO ()
+testPinchPublicationRefusal = do
+  pinched <- regionMesh "published pinch" 3 3
+  let selected =
+        regionFaceSatisfies pinched $ \(Point x y) ->
+          let cell = (floor x :: Int, floor y :: Int)
+           in cell /= (0, 0) && cell /= (1, 1)
+  case labelledPlanarLayer False pinched selected of
+    Left (RegionBoundaryObstruction BoundaryPinch {}) -> pure ()
+    other -> fail ("pinched publication produced " <> show other)
+
+assertComponentShape
+  :: Ord label
+  => String
+  -> label
+  -> Int
+  -> [Int]
+  -> PlanarLayer label
+  -> IO ()
+assertComponentShape label regionLabel expectedOuterVertices expectedHoleVertices layer =
+  case componentsFor regionLabel layer of
+    [component] -> do
+      assertEqual
+        (label <> " outer vertices")
+        expectedOuterVertices
+        (length (exactLoopPoints (polygonOuterLoop component)))
+      assertEqual
+        (label <> " hole vertices")
+        expectedHoleVertices
+        (map (length . exactLoopPoints) (polygonHoleLoops component))
+    components ->
+      fail (label <> ": expected one component, got " <> show (length components))
+
+componentsFor :: Ord label => label -> PlanarLayer label -> [PolygonComponent]
+componentsFor label =
+  maybe [] planarRegionComponents . Map.lookup label . planarLayerRegions
diff --git a/test/serialization/Moonlight/Triangulation/SerializationSpec.hs b/test/serialization/Moonlight/Triangulation/SerializationSpec.hs
--- a/test/serialization/Moonlight/Triangulation/SerializationSpec.hs
+++ b/test/serialization/Moonlight/Triangulation/SerializationSpec.hs
@@ -1,26 +1,34 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 
 -- | The serialization slice: the versioned binary envelope and its refusals.
 module Moonlight.Triangulation.SerializationSpec (tests) where
 
 import Control.DeepSeq (NFData)
-import Control.Monad (forM_, unless)
+import Control.Monad (unless)
 import Data.Binary (Binary)
+import Data.Binary.Put (putWord16be, putWord64be, runPut)
 import qualified Data.ByteString.Lazy as BL
+import Data.Foldable (traverse_)
 import qualified Data.Vector as V
+import Data.Word (Word16, Word64)
 import GHC.Generics (Generic)
 import Moonlight.Triangulation
 import Moonlight.Triangulation.Serialization
+import Moonlight.Triangulation.Types (KnownConstraintMode)
 import Support (assertEqual, assertValid, requireRight)
 
 tests :: IO ()
 tests = do
   testRoundTrip
+  testDegenerateCardinalityRoundTrips
+  testConstrainedRoundTrip
   testIndependentPayloadGeometryRoundTrip
   testPointPayloadRoundTrip
+  testRejectsHostileStructuralPrefixes
   testRejectsCorruption
   putStrLn "all serialization tests passed"
 
@@ -36,6 +44,13 @@
 
 type SerialTriangulation = Triangulation 'Unconstrained SerialVertex Int Bool String
 
+testDecodingBudget :: DecodingBudget
+testDecodingBudget =
+  DecodingBudget
+    { decodingMaximumInputBytes = 10_000_000
+    , decodingMaximumSectionElements = 10_000_000
+    }
+
 source :: IO SerialTriangulation
 source = do
   let defaults = ElementDefaults (3 :: Int) True ("face" :: String)
@@ -53,10 +68,35 @@
   original <- source
   let bytes = encodeTriangulation original
   unless (BL.length bytes > 0) $ fail "serialization produced an empty payload"
-  roundTrip <- requireRight "serialization round trip" (decodeTriangulation bytes :: Either SerializationError SerialTriangulation)
-  assertEqual "serialization equality" original roundTrip
-  assertValid "serialization round trip" roundTrip
+  assertSerializationRoundTrip "serialization" original
 
+testDegenerateCardinalityRoundTrips :: IO ()
+testDegenerateCardinalityRoundTrips =
+  traverse_
+    roundTripGeometry
+    [ ("empty", V.empty)
+    , ("singleton", V.singleton (Point 0 0))
+    , ("segment", V.fromList [Point 0 0, Point 1 0])
+    , ("collinear chain", V.fromList [Point 0 0, Point 1 0, Point 2 0, Point 3 0])
+    ]
+ where
+  roundTripGeometry (label, points) = do
+    original <- requireRight (label <> " serialization source") (delaunayGeometry points)
+    assertSerializationRoundTrip (label <> " serialization") original
+
+testConstrainedRoundTrip :: IO ()
+testConstrainedRoundTrip = do
+  built <-
+    requireRight
+      "constrained serialization source"
+      ( constrainedDelaunay
+          unitElementDefaults
+          (V.fromList [Point 0 0, Point 2 0, Point 2 2, Point 0 2])
+          (V.singleton (0, 2))
+      )
+  let original = buildTriangulation built
+  assertSerializationRoundTrip "constrained serialization" original
+
 -- Vertex payload positions are annotations after ingestion. Serialization must
 -- therefore preserve the fixed geometry and the independently edited payload,
 -- rather than letting the latter reauthor the former on decode.
@@ -73,18 +113,8 @@
     (vertexPoint geometry vertex) (vertexPoint original vertex)
   assertEqual "independent payload position is stored"
     (Point 91 73) (serialPosition (vertexData original vertex))
-  roundTrip <-
-    requireRight
-      "independent payload serialization"
-      (decodeTriangulation (encodeTriangulation original) :: Either SerializationError SerialTriangulation)
-  assertEqual "independent geometry, payload, and topology round trip" original roundTrip
-  assertValid "independent payload serialization" roundTrip
-  positionlessRoundTrip <-
-    requireRight
-      "positionless payload serialization"
-      (decodeTriangulation (encodeTriangulation positionless) :: Either SerializationError (Triangulation 'Unconstrained Int Int Bool String))
-  assertEqual "positionless payload round trip" positionless positionlessRoundTrip
-  assertValid "positionless payload serialization" positionlessRoundTrip
+  assertSerializationRoundTrip "independent payload serialization" original
+  assertSerializationRoundTrip "positionless payload serialization" positionless
 
 testPointPayloadRoundTrip :: IO ()
 testPointPayloadRoundTrip = do
@@ -99,13 +129,85 @@
     (vertexPoint geometry vertex) (vertexPoint original vertex)
   assertEqual "point payload is stored"
     (Point 13 17) (vertexData original vertex)
-  roundTrip <-
+  assertSerializationRoundTrip "point payload serialization" original
+
+assertSerializationRoundTrip
+  :: ( KnownConstraintMode mode
+     , Binary vertex
+     , Binary directed
+     , Binary undirected
+     , Binary face
+     , Eq (Triangulation mode vertex directed undirected face)
+     , Show (Triangulation mode vertex directed undirected face)
+     )
+  => String
+  -> Triangulation mode vertex directed undirected face
+  -> IO ()
+assertSerializationRoundTrip label original = do
+  decoded <-
     requireRight
-      "point payload serialization"
-      (decodeTriangulation (encodeTriangulation original) :: Either SerializationError (Triangulation 'Unconstrained (Point) () () ()))
-  assertEqual "point payload serialization equality" original roundTrip
-  assertValid "point payload serialization round trip" roundTrip
+      (label <> " round trip")
+      (decodeTriangulation testDecodingBudget trustedBinaryPayloadDecoders (encodeTriangulation original))
+  assertEqual (label <> " equality") original decoded
+  assertValid (label <> " validity") decoded
 
+-- Counts are one prefix precisely so these refusals precede all default and
+-- payload decoders. The hostile fixtures use @()@, whose lawful decoder consumes
+-- no bytes, to exercise the formerly allocative attack rather than relying on
+-- truncation to save the process.
+testRejectsHostileStructuralPrefixes :: IO ()
+testRejectsHostileStructuralPrefixes =
+  traverse_
+    (\(label, budget, bytes, failure) ->
+       assertDecodeFailure label budget bytes failure)
+    [ ( "input byte budget"
+      , DecodingBudget 43 10_000
+      , structuralPrefix 6 0 0 1 0
+      , InputByteBudgetExceeded 44 43
+      )
+    , ( "section element budget"
+      , DecodingBudget 1_000 1_000_000
+      , structuralPrefix 6 1_000_000 1_999_998 1 0
+      , DecodedSectionBudgetExceeded 15_999_990 1_000_000
+      )
+    , ("directed edge parity", testDecodingBudget, structuralPrefix 6 0 1 1 0, SerializedDirectedEdgeCountOdd 1)
+    , ("missing outer face", testDecodingBudget, structuralPrefix 6 0 0 0 0, SerializedMissingOuterFace)
+    , ("constraint count relationship", testDecodingBudget, structuralPrefix 6 0 2 1 2, SerializedConstraintCountExceedsEdges 2 1)
+    , ("planar cardinality relationship", testDecodingBudget, structuralPrefix 6 2 0 1 0, SerializedPlanarCardinalityMismatch 2 0 1)
+    , ("fixed body lower bound", testDecodingBudget, structuralPrefix 6 1 0 1 0, SerializedFixedBodyTooShort 0 24)
+    , ("host Int vertex count", DecodingBudget 1_000 maxBound, structuralPrefix 6 maxBound 0 1 0, EncodedCountExceedsInt SerializedVertexCount maxBound)
+    , ( "packed vertex count"
+      , DecodingBudget 1_000 maxBound
+      , structuralPrefix 6 4_294_967_296 0 1 0
+      , EncodedCountExceedsPackedIndex SerializedVertexCount 4_294_967_296 4_294_967_295
+      )
+    , ("version 5", testDecodingBudget, structuralPrefix 5 0 0 1 0, UnsupportedFormatVersion 5)
+    ]
+
+assertDecodeFailure
+  :: String
+  -> DecodingBudget
+  -> BL.ByteString
+  -> SerializationError
+  -> IO ()
+assertDecodeFailure label budget bytes expected =
+  assertEqual
+    label
+    (Left expected)
+    ( decodeTriangulation budget trustedBinaryPayloadDecoders bytes
+        :: Either SerializationError (Triangulation 'Unconstrained () () () ())
+    )
+
+structuralPrefix :: Word16 -> Word64 -> Word64 -> Word64 -> Word64 -> BL.ByteString
+structuralPrefix version vertexCount directedEdgeCount faceCount constraintCount =
+  runPut $ do
+    putWord64be 0x5350414445485307
+    putWord16be version
+    putWord16be 2
+    traverse_
+      putWord64be
+      [vertexCount, directedEdgeCount, faceCount, constraintCount]
+
 -- The header is the part of the stream that is structurally constrained: magic,
 -- version, constraint mode and coordinate encoding each have exactly one admissible
 -- byte pattern, so every mutation of them must be refused. Beyond the header
@@ -119,8 +221,9 @@
   original <- source
   let bytes = encodeTriangulation original
       size = BL.length bytes
-      headerSize = 8 + 2 + 1 + 1
-      decode candidate = decodeTriangulation candidate :: Either SerializationError SerialTriangulation
+      envelopeSize = 8 + 2 + 1 + 1
+      structuralPrefixSize = envelopeSize + 4 * 8
+      decode candidate = decodeTriangulation testDecodingBudget trustedBinaryPayloadDecoders candidate :: Either SerializationError SerialTriangulation
       flipAt offset =
         BL.concat [BL.take offset bytes, BL.singleton (BL.index bytes offset + 1), BL.drop (offset + 1) bytes]
       rejects :: String -> BL.ByteString -> IO ()
@@ -136,12 +239,18 @@
     Left (InvalidFormatMagic _) -> pure ()
     other -> fail ("magic corruption produced " <> show other)
   rejects "an empty payload" BL.empty
-  forM_ [1 .. size] $ \dropped ->
-    rejects ("a payload truncated by " <> show dropped) (BL.take (size - dropped) bytes)
-  forM_ [0 .. headerSize - 1] $ \offset ->
-    rejects ("a header byte flipped at offset " <> show offset) (flipAt offset)
-  forM_ [headerSize .. size - 1] $ \offset ->
+  traverse_
+    (\dropped -> rejects ("a payload truncated by " <> show dropped) (BL.take (size - dropped) bytes))
+    [1 .. size]
+  traverse_
+    (\offset -> rejects ("an envelope byte flipped at offset " <> show offset) (flipAt offset))
+    [0 .. envelopeSize - 1]
+  traverse_
+    (\offset -> rejects ("a structural-prefix byte flipped at offset " <> show offset) (flipAt offset))
+    [envelopeSize .. structuralPrefixSize - 1]
+  traverse_ (\offset ->
     case decode (flipAt offset) of
       Left _ -> pure ()
       Right decoded ->
         assertValid ("a byte flipped at offset " <> show offset <> " decoded to") decoded
+    ) [structuralPrefixSize .. size - 1]
diff --git a/test/support/Support.hs b/test/support/Support.hs
--- a/test/support/Support.hs
+++ b/test/support/Support.hs
@@ -1,19 +1,29 @@
--- | Assertions shared by every test slice. Nothing here may depend on an
--- optional package flag, so that the minimal core configuration compiles the
--- same helpers the full configuration does.
+-- | Assertions and exact planar fixtures shared by the test slices. Nothing
+-- here may depend on an optional package flag, so the minimal configuration
+-- compiles the same helpers as the full one.
 module Support
   ( requireRight
   , requireQueryPoint
   , assertEqual
   , assertValid
+  , integerPoint
+  , rectangleLoop
+  , rectangleComponent
   ) where
 
 import Control.Monad (unless)
+import Data.List.NonEmpty (NonEmpty (..))
 import Moonlight.Triangulation
-  ( Point
+  ( ExactLoop
+  , ExactPoint
+  , Point
+  , PolygonComponent
   , QueryPoint
   , Triangulation
+  , exactLoop
+  , exactPoint
   , mkQueryPoint
+  , polygonComponent
   , validateTriangulation
   )
 
@@ -22,7 +32,7 @@
   Left failure -> fail (label <> ": " <> show failure)
   Right result -> pure result
 
-requireQueryPoint :: String -> Point -> IO (QueryPoint)
+requireQueryPoint :: String -> Point -> IO QueryPoint
 requireQueryPoint label = requireRight label . mkQueryPoint
 
 assertEqual :: (Eq value, Show value) => String -> value -> value -> IO ()
@@ -35,3 +45,29 @@
   case validateTriangulation triangulation of
     [] -> pure ()
     violations -> fail (label <> " invariant violations: " <> show violations)
+
+integerPoint :: Integer -> Integer -> ExactPoint
+integerPoint x y = exactPoint (fromInteger x) (fromInteger y)
+
+rectangleLoop :: Integer -> Integer -> Integer -> Integer -> IO ExactLoop
+rectangleLoop minimumX minimumY maximumX maximumY =
+  requireRight
+    "rectangle loop"
+    ( exactLoop
+        ( integerPoint minimumX minimumY
+            :| [ integerPoint maximumX minimumY
+               , integerPoint maximumX maximumY
+               , integerPoint minimumX maximumY
+               ]
+        )
+    )
+
+rectangleComponent
+  :: Integer
+  -> Integer
+  -> Integer
+  -> Integer
+  -> IO PolygonComponent
+rectangleComponent minimumX minimumY maximumX maximumY =
+  rectangleLoop minimumX minimumY maximumX maximumY
+    >>= requireRight "rectangle component" . (`polygonComponent` [])
diff --git a/weeder.toml b/weeder.toml
--- a/weeder.toml
+++ b/weeder.toml
@@ -11,6 +11,10 @@
   '^Moonlight\.Triangulation\.LineSideInfo$',
   '^Moonlight\.Triangulation\.Types$',
   '^Moonlight\.Triangulation\.Math$',
+  '^Moonlight\.Triangulation\.Exact$',
+  '^Moonlight\.Triangulation\.CellSet$',
+  '^Moonlight\.Triangulation\.Region$',
+  '^Moonlight\.Triangulation\.Valuation$',
   '^Moonlight\.Triangulation\.Interop$',
   '^Moonlight\.Triangulation\.Dcel$',
   '^Moonlight\.Triangulation\.Payload$',
@@ -31,6 +35,8 @@
   '^Moonlight\.Triangulation\.Removal$',
   '^Moonlight\.Triangulation\.Session$',
   '^Moonlight\.Triangulation\.Cdt$',
+  '^Moonlight\.Triangulation\.Minkowski$',
+  '^Moonlight\.Triangulation\.Overlay$',
   '^Moonlight\.Triangulation\.Refinement$',
   '^Moonlight\.Triangulation\.SetAlgebra$',
   '^Moonlight\.Triangulation\.Parallel$',
