diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,36 @@
 # Changelog for data-sketches
 
+## 0.4.0.1 — 2026-04-08
+
+### Internal cleanup
+
+- Removed dead pure-Haskell REQ internals that were superseded by the C
+  backend in 0.4.0.0. No public API changes — `insert`, `quantile`, `rank`,
+  `countWithCriterion`, and all other exports continue to work identically.
+
+- Dropped unused `mwc-random`, `vector-algorithms`, `mtl`, and
+  `hspec-junit-formatter` dependencies.
+
+### Performance
+
+- HLL `estimate` is ~2.6x faster (C backend: `ldexp` replaced with bit
+  manipulation; NEON/SSE2 intrinsics for merge and zero-counting).
+
+- Added estimate, merge, and query benchmarks for HLL, KLL, and Count-Min
+  sketches (criterion suite).
+
+### Test improvements
+
+- Added `countWithCriterion` coverage: LT/LE threshold counting, boundary
+  behavior at min/max, duplicate values, estimation mode, and non-finite
+  exception handling.
+
+- Added quantile monotonicity tests: verifies `quantile(r1) <= quantile(r2)`
+  for `r1 <= r2` in exact mode, HRA estimation mode, and LRA estimation mode.
+
+- Added estimation mode capacity growth tests: 10K inserts with accuracy
+  checks; batch vs sequential insert equivalence.
+
 ## 0.4.0.0
 
 ### New sketch families
diff --git a/bench/Bench.hs b/bench/Bench.hs
--- a/bench/Bench.hs
+++ b/bench/Bench.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
 
-import Control.Monad (void)
+import Control.Monad (void, forM_)
 import Criterion.Main
 import Criterion.Types
 import Data.Word
@@ -97,6 +97,10 @@
       [ bench "100-queries-from-10000" $ perRunEnv (mkKllWith 10000) $ \sk ->
           loopDoubleStep (void . KLL.rank sk) 0 9900 100
       ]
+    , bgroup "merge"
+      [ bench "10000-into-10000" $ perRunEnv (mkKllPair 10000) $ \(a, b) ->
+          KLL.merge a b
+      ]
     ]
   , bgroup "HLL"
     [ bgroup "insert"
@@ -115,6 +119,20 @@
       , bench "100000" $ perRunEnv (HLL.mkHllSketch 12) $ \sk ->
           HLL.insertBatch sk (word64Vec 100000)
       ]
+    , bgroup "estimate"
+      [ bench "p=12-after-10000" $ perRunEnv (mkHllWith 12 10000) $ \sk ->
+          void (HLL.estimate sk)
+      , bench "p=14-after-10000" $ perRunEnv (mkHllWith 14 10000) $ \sk ->
+          void (HLL.estimate sk)
+      , bench "p=12-after-100000" $ perRunEnv (mkHllWith 12 100000) $ \sk ->
+          void (HLL.estimate sk)
+      ]
+    , bgroup "merge"
+      [ bench "p=12" $ perRunEnv (mkHllPair 12 10000) $ \(a, b) ->
+          HLL.merge a b
+      , bench "p=14" $ perRunEnv (mkHllPair 14 10000) $ \(a, b) ->
+          HLL.merge a b
+      ]
     ]
   , bgroup "CountMin"
     [ bgroup "insert"
@@ -123,6 +141,14 @@
       , bench "100000" $ perRunEnv (CM.mkCountMinSketch 0.001 0.01) $ \sk ->
           loopWord64 (CM.insert sk) 1 100000
       ]
+    , bgroup "estimate"
+      [ bench "1000-queries" $ perRunEnv (mkCmsWith 100000) $ \sk ->
+          loopWord64 (void . CM.estimate sk) 1 1000
+      ]
+    , bgroup "merge"
+      [ bench "e=0.001-d=0.01" $ perRunEnv (mkCmsPair 100000) $ \(a, b) ->
+          CM.merge a b
+      ]
     ]
   ]
 
@@ -137,3 +163,39 @@
   sk <- KLL.mkKllSketch 200
   loopDouble (KLL.insert sk) 1 (fromIntegral n)
   pure sk
+
+mkKllPair :: Int -> IO (KLL.KllSketch (PrimState IO), KLL.KllSketch (PrimState IO))
+mkKllPair n = do
+  a <- KLL.mkKllSketch 200
+  b <- KLL.mkKllSketch 200
+  loopDouble (KLL.insert a) 1 (fromIntegral n)
+  loopDouble (KLL.insert b) (fromIntegral n + 1) (fromIntegral (n * 2))
+  pure (a, b)
+
+mkHllWith :: Int -> Int -> IO (HLL.HllSketch (PrimState IO))
+mkHllWith p n = do
+  sk <- HLL.mkHllSketch p
+  loopWord64 (HLL.insert sk) 1 (fromIntegral n)
+  pure sk
+
+mkHllPair :: Int -> Int -> IO (HLL.HllSketch (PrimState IO), HLL.HllSketch (PrimState IO))
+mkHllPair p n = do
+  a <- HLL.mkHllSketch p
+  b <- HLL.mkHllSketch p
+  loopWord64 (HLL.insert a) 1 (fromIntegral n)
+  loopWord64 (HLL.insert b) (fromIntegral n + 1) (fromIntegral (n * 2))
+  pure (a, b)
+
+mkCmsWith :: Int -> IO (CM.CountMinSketch (PrimState IO))
+mkCmsWith n = do
+  sk <- CM.mkCountMinSketch 0.001 0.01
+  loopWord64 (CM.insert sk) 1 (fromIntegral n)
+  pure sk
+
+mkCmsPair :: Int -> IO (CM.CountMinSketch (PrimState IO), CM.CountMinSketch (PrimState IO))
+mkCmsPair n = do
+  a <- CM.mkCountMinSketch 0.001 0.01
+  b <- CM.mkCountMinSketch 0.001 0.01
+  loopWord64 (CM.insert a) 1 (fromIntegral n)
+  loopWord64 (CM.insert b) (fromIntegral n + 1) (fromIntegral (n * 2))
+  pure (a, b)
diff --git a/data-sketches.cabal b/data-sketches.cabal
--- a/data-sketches.cabal
+++ b/data-sketches.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           data-sketches
-version:        0.4.0.0
+version:        0.4.0.1
 synopsis:       Stochastic streaming algorithms for approximate computation on large datasets. Includes KLL, HLL, Theta, Count-Min, and REQ sketches.
 description:    Please see the README on GitHub at <https://github.com/iand675/datasketches-haskell#readme>
 category:       Data
@@ -50,25 +50,19 @@
       TypeOperators
   build-depends:
       base >=4.7 && <5
-    , data-sketches-core ==0.2.*
+    , data-sketches-core ==0.3.*
     , ghc-prim
-    , mtl
-    , mwc-random
     , primitive
     , vector
-    , vector-algorithms
   default-language: Haskell2010
 
 test-suite data-sketches-test
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
-      AuxiliarySpec
       BugFixSpec
-      CompactorSpec
       CountMinSpec
       CrossValidationSpec
-      DoubleBufferSpec
       HyperLogLogSpec
       KllSpec
       ProofCheckSpec
@@ -95,16 +89,12 @@
     , ghc-prim
     , hedgehog
     , hspec
-    , hspec-junit-formatter
-    , mtl
-    , mwc-random
     , pretty-show
     , primitive
     , process
     , statistics
     , temporary
     , vector
-    , vector-algorithms
   default-language: Haskell2010
 
 benchmark bench
@@ -129,9 +119,6 @@
     , data-sketches
     , data-sketches-core
     , ghc-prim
-    , mtl
-    , mwc-random
     , primitive
     , vector
-    , vector-algorithms
   default-language: Haskell2010
diff --git a/src/DataSketches/Quantiles/RelativeErrorQuantile.hs b/src/DataSketches/Quantiles/RelativeErrorQuantile.hs
--- a/src/DataSketches/Quantiles/RelativeErrorQuantile.hs
+++ b/src/DataSketches/Quantiles/RelativeErrorQuantile.hs
@@ -96,11 +96,9 @@
 import Control.Monad.Primitive ( PrimMonad(PrimState) )
 import Data.Word ( Word32, Word64 )
 import DataSketches.Quantiles.RelativeErrorQuantile.Types
-    ( RankAccuracy(..) )
+    ( RankAccuracy(..), DoubleIsNonFiniteException(..) )
 import DataSketches.Quantiles.RelativeErrorQuantile.Internal.Constants
     ( fixRseFactor, initNumberOfSections, relRseFactor )
-import DataSketches.Quantiles.RelativeErrorQuantile.Internal.DoubleBuffer
-    ( DoubleIsNonFiniteException(..) )
 import DataSketches.Quantiles.RelativeErrorQuantile.CInternal
 import qualified Data.List
 import qualified Data.Vector.Storable as VS
diff --git a/test/AuxiliarySpec.hs b/test/AuxiliarySpec.hs
deleted file mode 100644
--- a/test/AuxiliarySpec.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-module AuxiliarySpec where
-
-import Data.Primitive.MutVar
-import qualified Data.Vector.Unboxed.Mutable as MUVector
-import DataSketches.Quantiles.RelativeErrorQuantile.Internal.Auxiliary
-import qualified DataSketches.Quantiles.RelativeErrorQuantile.Internal.Auxiliary as Aux
-import DataSketches.Quantiles.RelativeErrorQuantile.Types
-import Test.Hspec
-import qualified Data.Vector.Unboxed as U
-import qualified Data.List
-import DataSketches.Quantiles.RelativeErrorQuantile.Internal.DoubleBuffer
-
-
-spec :: Spec
-spec = do
-  mapM_ checkMergeSortIn [HighRanksAreAccurate, LowRanksAreAccurate]
-
-checkMergeSortIn :: RankAccuracy -> Spec
-checkMergeSortIn ra = specify ("mergeSortIn works. hra=" ++ show ra) $ do
-  let hraBool = case ra of
-        HighRanksAreAccurate -> True
-        LowRanksAreAccurate -> False
-  let oddItems = [1,3..11] -- 6 items
-  let evenItems = [2,4..12]
-
-  buf1 <- mkBuffer 25 0 hraBool
-  mapM_ (append buf1) oddItems
-  
-  buf2 <- mkBuffer 25 0 hraBool
-  mapM_ (append buf2) evenItems
-
-  let n = 12
-  weightedItems <- newMutVar =<< MUVector.new 25  
-  let aux = MReqAuxiliary weightedItems ra n
-  Aux.mergeSortIn aux buf1 1 0
-  Aux.mergeSortIn aux buf2 2 6
-  (items, _) <- fmap U.unzip . U.freeze =<< readMutVar (mraWeightedItems aux)
-  let itemsToCheck = U.slice 0 (fromIntegral n) items
-  itemsToCheck `shouldBe` U.fromList (Data.List.sort (oddItems ++ evenItems))
diff --git a/test/BugFixSpec.hs b/test/BugFixSpec.hs
--- a/test/BugFixSpec.hs
+++ b/test/BugFixSpec.hs
@@ -1,75 +1,27 @@
 {-# LANGUAGE TypeApplications #-}
 module BugFixSpec where
 
-import Control.Monad (forM_, replicateM_)
-import Control.Monad.Primitive (PrimState)
-import Data.Word
-import qualified Data.Vector.Unboxed.Mutable as MUVector
+import Control.Monad (forM_)
 import Test.Hspec
 import DataSketches.Quantiles.RelativeErrorQuantile hiding (null, minimum, maximum)
 import qualified DataSketches.Quantiles.RelativeErrorQuantile as REQ
-import DataSketches.Quantiles.RelativeErrorQuantile.Types
-import DataSketches.Quantiles.RelativeErrorQuantile.Internal.DoubleBuffer
 
 spec :: Spec
 spec = do
-  describe "Bug fix: off-by-one in getCountWithCriterion (spaceAtBottom=False)" $ do
-    -- These tests exercise the Haskell-internal DoubleBuffer directly.
-    specify "getCountWithCriterion reads only within active region (spaceAtBottom=False)" $ do
-      buf <- mkBuffer 16 0 False
-      mapM_ (append buf) [10, 20, 30, 40, 50]
-      sort buf
-
-      vec <- getVector buf
-      forM_ [5..15] $ \i -> MUVector.unsafeWrite vec i 0
-
-      cnt <- getCountWithCriterion buf 55 (:<)
-      cnt `shouldBe` 5
-
-    specify "getCountWithCriterion at upper boundary (spaceAtBottom=False)" $ do
-      buf <- mkBuffer 16 0 False
-      mapM_ (append buf) [10, 20, 30, 40, 50]
-      sort buf
-
-      vec <- getVector buf
-      forM_ [5..15] $ \i -> MUVector.unsafeWrite vec i 25
-
-      cnt <- getCountWithCriterion buf 50 (:<)
-      cnt `shouldBe` 4
-
-      cnt2 <- getCountWithCriterion buf 50 (:<=)
-      cnt2 `shouldBe` 5
-
-    specify "getCountWithCriterion at lower boundary (spaceAtBottom=False)" $ do
-      buf <- mkBuffer 16 0 False
-      mapM_ (append buf) [10, 20, 30, 40, 50]
-      sort buf
-
-      vec <- getVector buf
-      forM_ [5..15] $ \i -> MUVector.unsafeWrite vec i 0
-
-      cnt <- getCountWithCriterion buf 5 (:<)
-      cnt `shouldBe` 0
-
-      cnt2 <- getCountWithCriterion buf 10 (:<)
-      cnt2 `shouldBe` 0
-
-      cnt3 <- getCountWithCriterion buf 10 (:<=)
-      cnt3 `shouldBe` 1
-
-    specify "rank of value above max is correct with LowRanksAreAccurate" $ do
+  describe "Bug fix: rank boundary conditions with LowRanksAreAccurate" $ do
+    specify "rank of value above max is correct" $ do
       sk <- mkReqSketch 12 LowRanksAreAccurate
       forM_ [1..50 :: Double] $ insert sk
       r <- rank sk 100
       r `shouldBe` 1.0
 
-    specify "rank of value below min is 0 with LowRanksAreAccurate" $ do
+    specify "rank of value below min is 0" $ do
       sk <- mkReqSketch 12 LowRanksAreAccurate
       forM_ [10..60 :: Double] $ insert sk
       r <- rank sk 5
       r `shouldBe` 0.0
 
-    specify "ranks are correct for known values with LowRanksAreAccurate" $ do
+    specify "ranks are correct for known values" $ do
       sk <- mkReqSketch 50 LowRanksAreAccurate
       let values = [5, 5, 5, 6, 6, 6, 7, 8, 8, 8 :: Double]
       mapM_ (insert sk) values
diff --git a/test/CompactorSpec.hs b/test/CompactorSpec.hs
deleted file mode 100644
--- a/test/CompactorSpec.hs
+++ /dev/null
@@ -1,9 +0,0 @@
-module CompactorSpec where
-
-import DataSketches.Quantiles.RelativeErrorQuantile.Internal.Compactor
-import Test.Hspec
-
-spec :: Spec
-spec = do
-  specify "nearestEven behaves as expected" $ do
-    nearestEven (-0.9) `shouldBe` 0
diff --git a/test/DoubleBufferSpec.hs b/test/DoubleBufferSpec.hs
deleted file mode 100644
--- a/test/DoubleBufferSpec.hs
+++ /dev/null
@@ -1,116 +0,0 @@
-module DoubleBufferSpec where
-
-import Control.Monad
-import DataSketches.Quantiles.RelativeErrorQuantile.Internal.DoubleBuffer
-import DataSketches.Quantiles.RelativeErrorQuantile.Types
-import qualified Data.Vector.Unboxed as UV
-import Test.Hspec
-import qualified Data.List
-
-spec :: Spec
-spec = do
-  describe "Merge Sort" $ do
-    let mergeSortTestImpl direction = do
-          buf <- mkBuffer 16 0 direction
-          forM [0..31] (append buf)
-          buf2 <- copyBuffer buf
-          mergeSortIn buf buf2
-          v <- getVector buf
-          UV.freeze v
-    it "works upwards" $ do
-      v <- mergeSortTestImpl False
-      v `shouldBe` UV.fromList (Data.List.sort ([0..31] ++ [0..31]))
-    it "works downwards" $ do
-      v <- mergeSortTestImpl True
-      v `shouldBe` UV.fromList (Data.List.sort ([0..31] ++ [0..31]))
-  describe "getEvensOrOdds" $ do
-    checkGetEvensOrOdds False False
-    checkGetEvensOrOdds False True
-    checkGetEvensOrOdds True False
-    checkGetEvensOrOdds True True
-  describe "checkAppendAndSpaceTop" $ do
-    checkAppendAndSpace True
-    checkAppendAndSpace False
-  describe "checkEnsureCapacity" $ do
-    mapM_ checkEnsureCapacity [True, False]
-  describe "checkCountLessThanImpl" $ do
-    mapM_ checkCountLessThanImpl [True, False]
-
-checkGetEvensOrOdds :: Bool -> Bool -> Spec
-checkGetEvensOrOdds odds spaceAtBottom = 
-  it ("works for odds=" ++ show odds ++ " spaceAtBottom=" ++ show spaceAtBottom) $ do
-    buf <- mkBuffer cap 0 spaceAtBottom
-    forM_ [0..cap `div` 2] (append buf . fromIntegral)
-    out <- getEvensOrOdds buf 0 (cap `div` 2) odds
-    v <- UV.freeze =<< getVector out
-    v `shouldSatisfy` UV.all (\x -> if odds then floor x `mod` 2 == 1 else floor x `mod` 2 == 0)
-  where
-    cap = 16
-
-checkAppendAndSpace :: Bool -> Spec
-checkAppendAndSpace spaceAtBottom = it ("works for spaceAtBottom=" ++ show spaceAtBottom) $ do
-  buf <- mkBuffer 2 2 spaceAtBottom
-  do
-    count_ <- getCount buf
-    count_ `shouldBe` 0
-    capacity_ <- getCapacity buf 
-    capacity_ `shouldBe` 2
-    space_ <- getSpace buf
-    space_ `shouldBe` 2
-  append buf 1
-  do
-    count_ <- getCount buf
-    count_ `shouldBe` 1
-    capacity_ <- getCapacity buf 
-    capacity_ `shouldBe` 2
-    space_ <- getSpace buf
-    space_ `shouldBe` 1
-  append buf 2
-  do
-    count_ <- getCount buf
-    count_ `shouldBe` 2
-    capacity_ <- getCapacity buf 
-    capacity_ `shouldBe` 2
-    space_ <- getSpace buf
-    space_ `shouldBe` 0
-  append buf 3
-  do
-    count_ <- getCount buf
-    count_ `shouldBe` 3
-    capacity_ <- getCapacity buf 
-    capacity_ `shouldBe` 5
-    space_ <- getSpace buf
-    space_ `shouldBe` 2
-
-checkEnsureCapacity :: Bool -> Spec
-checkEnsureCapacity spaceAtBottom = it ("works for spaceAtBottom=" ++ show spaceAtBottom) $ do
-  buf <- mkBuffer 4 2 spaceAtBottom
-  append buf 2
-  append buf 1
-  append buf 3
-  ensureCapacity buf 8
-  sort buf
-  x1 <- buf ! 0
-  x1 `shouldBe` 1
-  x2 <- buf ! 1
-  x2 `shouldBe` 2
-  x3 <- buf ! 2
-  x3 `shouldBe` 3
-
-checkCountLessThanImpl :: Bool -> Spec
-checkCountLessThanImpl spaceAtBottom = it ("works for spaceAtBottom=" ++ show spaceAtBottom) $ do 
-  buf <- mkBuffer 7 0 spaceAtBottom
-  mapM_ (append buf) [1..7]
-  sort buf
-  buf2 <- mkBuffer 7 0 spaceAtBottom
-  mergeSortIn buf2 buf
-  do
-    count_ <- getCountWithCriterion buf2 4 (:<)
-    count_ `shouldBe` 3
-  mergeSortIn buf2 buf
-  do
-    count_ <- getCountWithCriterion buf2 4 (:<)
-    count_ `shouldBe` 6
-  do
-    count_ <- getCount buf2
-    count_ `shouldBe` 14
diff --git a/test/RelativeErrorQuantileSpec.hs b/test/RelativeErrorQuantileSpec.hs
--- a/test/RelativeErrorQuantileSpec.hs
+++ b/test/RelativeErrorQuantileSpec.hs
@@ -4,12 +4,14 @@
 
 import Control.Monad
 import Control.Monad.Primitive
-import DataSketches.Quantiles.RelativeErrorQuantile
+import DataSketches.Quantiles.RelativeErrorQuantile hiding (null, minimum, maximum)
+import qualified DataSketches.Quantiles.RelativeErrorQuantile as REQ
 import DataSketches.Quantiles.RelativeErrorQuantile.Types
-import Data.List hiding (insert, null)
+import Data.List hiding (insert, null, minimum, maximum)
 import qualified Data.List
 import Data.Word
 import Test.Hspec
+import qualified Data.Vector.Storable as VS
 
 spec :: Spec
 spec = do
@@ -20,7 +22,7 @@
   specify "updating a sketch with NaN should ignore it" $ asIO $ do
     sk <- mkReqSketch 6 HighRanksAreAccurate
     insert sk (0 / 0)
-    isEmpty <- DataSketches.Quantiles.RelativeErrorQuantile.null sk
+    isEmpty <- REQ.null sk
     isEmpty `shouldBe` True
   specify "non finite rank should throw" $ asIO $ do
     let infinity = read "Infinity"::Double
@@ -90,6 +92,143 @@
       insert sk 1
       r <- quantile sk 0.5
       r `shouldBe` 1.0
+
+  describe "quantile monotonicity" $ do
+    specify "quantiles are non-decreasing for increasing normalized ranks (exact mode)" $ asIO $ do
+      sk <- mkReqSketch 50 HighRanksAreAccurate
+      mapM_ (insert sk) [1..100 :: Double]
+      let rankPoints = [0, 0.1 .. 1.0]
+      qs <- quantiles sk rankPoints
+      forM_ (zip qs (drop 1 qs)) $ \(q1, q2) ->
+        q2 `shouldSatisfy` (>= q1)
+
+    specify "quantiles are non-decreasing (estimation mode, HRA)" $ asIO $ do
+      sk <- mkReqSketch 6 HighRanksAreAccurate
+      mapM_ (insert sk) [1..2000 :: Double]
+      estimation <- isEstimationMode sk
+      estimation `shouldBe` True
+      let rankPoints = [0, 0.01 .. 1.0]
+      qs <- quantiles sk rankPoints
+      forM_ (zip qs (drop 1 qs)) $ \(q1, q2) ->
+        q2 `shouldSatisfy` (>= q1)
+
+    specify "quantiles are non-decreasing (estimation mode, LRA)" $ asIO $ do
+      sk <- mkReqSketch 6 LowRanksAreAccurate
+      mapM_ (insert sk) [1..2000 :: Double]
+      estimation <- isEstimationMode sk
+      estimation `shouldBe` True
+      let rankPoints = [0, 0.01 .. 1.0]
+      qs <- quantiles sk rankPoints
+      forM_ (zip qs (drop 1 qs)) $ \(q1, q2) ->
+        q2 `shouldSatisfy` (>= q1)
+
+  describe "countWithCriterion" $ do
+    specify "counts items strictly less than threshold (LT)" $ asIO $ do
+      sk <- mkReqSketch 50 HighRanksAreAccurate
+      mapM_ (insert sk) [10, 20, 30, 40, 50 :: Double]
+      setCriterionLT sk
+      cnt <- countWithCriterion sk 30
+      cnt `shouldBe` 2
+      cntAll <- countWithCriterion sk 55
+      cntAll `shouldBe` 5
+      cntNone <- countWithCriterion sk 5
+      cntNone `shouldBe` 0
+
+    specify "counts items less than or equal to threshold (LE)" $ asIO $ do
+      sk <- mkReqSketch 50 HighRanksAreAccurate
+      mapM_ (insert sk) [10, 20, 30, 40, 50 :: Double]
+      setCriterionLE sk
+      cnt <- countWithCriterion sk 30
+      cnt `shouldBe` 3
+      cntAll <- countWithCriterion sk 50
+      cntAll `shouldBe` 5
+      cntNone <- countWithCriterion sk 5
+      cntNone `shouldBe` 0
+
+    specify "boundary: at exact min value" $ asIO $ do
+      sk <- mkReqSketch 50 HighRanksAreAccurate
+      mapM_ (insert sk) [10, 20, 30, 40, 50 :: Double]
+      setCriterionLT sk
+      cntLt <- countWithCriterion sk 10
+      cntLt `shouldBe` 0
+      setCriterionLE sk
+      cntLe <- countWithCriterion sk 10
+      cntLe `shouldBe` 1
+
+    specify "boundary: at exact max value" $ asIO $ do
+      sk <- mkReqSketch 50 HighRanksAreAccurate
+      mapM_ (insert sk) [10, 20, 30, 40, 50 :: Double]
+      setCriterionLT sk
+      cntLt <- countWithCriterion sk 50
+      cntLt `shouldBe` 4
+      setCriterionLE sk
+      cntLe <- countWithCriterion sk 50
+      cntLe `shouldBe` 5
+
+    specify "with duplicate values" $ asIO $ do
+      sk <- mkReqSketch 50 HighRanksAreAccurate
+      mapM_ (insert sk) [1, 1, 1, 2, 2, 3 :: Double]
+      setCriterionLT sk
+      cntLt <- countWithCriterion sk 2
+      cntLt `shouldBe` 3
+      setCriterionLE sk
+      cntLe <- countWithCriterion sk 2
+      cntLe `shouldBe` 5
+
+    specify "works in estimation mode after compaction" $ asIO $ do
+      sk <- mkReqSketch 6 HighRanksAreAccurate
+      mapM_ (insert sk) [1..2000 :: Double]
+      estimation <- isEstimationMode sk
+      estimation `shouldBe` True
+      n <- count sk
+      n `shouldBe` 2000
+      setCriterionLE sk
+      cntMax <- countWithCriterion sk 2000
+      cntMax `shouldBe` n
+      setCriterionLT sk
+      cntMin <- countWithCriterion sk 0
+      cntMin `shouldBe` 0
+
+    specify "non-finite value throws DoubleIsNonFiniteException" $ asIO $ do
+      sk <- mkReqSketch 6 HighRanksAreAccurate
+      insert sk 1
+      countWithCriterion sk (0 / 0) `shouldThrow` (\(DoubleIsNonFiniteException _) -> True)
+
+  describe "estimation mode capacity growth" $ do
+    specify "10,000 inserts followed by correct quantile queries" $ asIO $ do
+      sk <- mkReqSketch 6 HighRanksAreAccurate
+      forM_ [1..10_000 :: Double] $ insert sk
+      n <- count sk
+      n `shouldBe` 10_000
+      estimation <- isEstimationMode sk
+      estimation `shouldBe` True
+      p50 <- quantile sk 0.5
+      p50 `shouldSatisfy` (\v -> v >= 4000 && v <= 6000)
+      p99 <- quantile sk 0.99
+      p99 `shouldSatisfy` (\v -> v >= 9800 && v <= 10_000)
+      mn <- REQ.minimum sk
+      mn `shouldBe` 1
+      mx <- REQ.maximum sk
+      mx `shouldBe` 10_000
+
+    specify "batch insert matches sequential insert" $ asIO $ do
+      skSeq <- mkReqSketch 12 HighRanksAreAccurate
+      forM_ [1..500 :: Double] $ insert skSeq
+
+      skBatch <- mkReqSketch 12 HighRanksAreAccurate
+      insertBatch skBatch (VS.fromList [1..500])
+
+      seqCount <- count skSeq
+      batchCount <- count skBatch
+      seqCount `shouldBe` batchCount
+
+      seqMin <- REQ.minimum skSeq
+      batchMin <- REQ.minimum skBatch
+      seqMin `shouldBe` batchMin
+
+      seqMax <- REQ.maximum skSeq
+      batchMax <- REQ.maximum skBatch
+      seqMax `shouldBe` batchMax
 
 
 bigTest :: Word32 -> Int -> Int -> RankAccuracy -> Bool -> Bool -> Spec
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,7 +1,4 @@
 import Test.Hspec
-import qualified AuxiliarySpec
-import qualified CompactorSpec
-import qualified DoubleBufferSpec
 import qualified ProofCheckSpec
 import qualified RelativeErrorQuantileSpec
 import qualified KllSpec
@@ -11,23 +8,16 @@
 import qualified BugFixSpec
 import qualified CrossValidationSpec
 import System.Environment
-import Test.HSpec.JUnit
 import Test.Hspec.Runner
 
 main :: IO ()
-main = 
+main =
       getArgs
-  >>= readConfig config
+  >>= readConfig defaultConfig
   >>= withArgs [] . runSpec specs
   >>= evaluateSummary
   where
-    config = defaultConfig 
-      { configFormat = Just $ junitFormat "test-results.xml" "data-sketches" 
-      }
     specs = do
-      describe "Auxiliary" AuxiliarySpec.spec
-      describe "Compactor" CompactorSpec.spec
-      describe "DoubleBuffer" DoubleBufferSpec.spec
       describe "ProofCheck" ProofCheckSpec.spec
       describe "RelativeErrorQuantile" RelativeErrorQuantileSpec.spec
       describe "KLL" KllSpec.spec
@@ -36,4 +26,3 @@
       describe "CountMin" CountMinSpec.spec
       describe "BugFix" BugFixSpec.spec
       describe "CrossValidation" CrossValidationSpec.spec
-
