---
title: "Liquidity shaping"
description: "The clamped quartic I-spline depth curve: exact storage, evaluation, integration, dispersion scaling and traversal, with the shipped central-normal plateau in closed form"
audience: tech
type: explanation
status: live
lang: en
updated: "2026-09-04"
publish: true
---
# Liquidity shaping

A pool's depth curve maps how much of a leg a trade consumes onto the price offset that trade pays. The on-chain object is a clamped quartic I-spline: monotone by construction, C2 at every knot, and integrable in constant time whatever the trade size. This page covers how a curve is stored and validated, how the shipped presets were fitted, how live dispersion scales one, and how a swap traverses it.

---

## 1. What the curve is

The curve is a function

$$y : [0, \text{BPS}] \to \text{pbps}\cdot Q, \qquad y \text{ nondecreasing}$$

mapping **cumulative depth** $x$ (in bps of the leg's reserves, so $x \in [0, 10000]$) to a
**relative price offset** $y$ (in pbps, stored at $Q = 10^9$ fixed point). It is not a price. It is not
denominated in any token. A quote is formed only when the offset is applied to the oracle mark:

$$p = m\cdot\frac{\text{PBPS} + \tilde y}{\text{PBPS}}$$

with $m$ the oracle mark and $\tilde y$ the offset after dispersion scaling (§6.1).

Four properties follow:

1. **The curve is anchor-free.** $x$ is a fraction of depth in the profile asset's own token
   units (§7), $y$ is a dimensionless relative offset. Nothing in the curve knows which
   asset a leg is anchored to. Only the *mark* carries denomination, and under the multi-anchor design
   the mark is attested in **parent** units. See
   [Anchor Path Pricing](/docs/1-1-3-anchor-path-pricing).
2. **The curve is monotone by construction.** Control weights are validated nondecreasing
   (`NUQuartic.sol`), which for a B-spline is equivalent to a nondecreasing integral at any
   degree. A non-monotone segment would mean negative marginal liquidity and cannot be
   expressed. This is condition **C2** of the cycle-safety proof: without it, a closed trading cycle
   through the tree could return more than it consumed.
3. **The density is C2.** Degree 4 with simple interior knots gives $y'$ two continuous derivatives at
   every knot. Marginal liquidity therefore varies smoothly, so there is no
   density step a taker can size a trade against, and monotonicity is a linear constraint on the
   control weights rather than a per-segment tangent condition (§3.1).
4. **The curve is shared, not per-asset.** Curves live in a per-pool preset table and assets point at
   one (§2.2). A refit updates every asset that references the preset.

---

## 2. Storage

### 2.1. Layout

```solidity
struct Curve {
    uint256 header;      // m(uint8) | b1..b13(13 x uint16) | median(uint16) | dispRefPbps(uint16) | flags(uint8)
    uint256[28] segs;    // segs[2i] = c0|c1|c2|c3 (4 x int64); segs[2i+1] = c4(int64) | S(int128)
}
```

`NUQuartic.sol`. One header slot plus two slots per segment, at most 14 segments
(`MAX_SEGS`, `NUQuartic.sol`: 13 interior boundaries in the header plus the constant right edge).

| Field | Bits | Meaning |
|-------|------|---------|
| `m` | header[0:8] | Segment count, 1 to 14 |
| `b_1..b_13` | header[8:216] | **Interior** right boundaries in x, uint16 each |
| `median` | header[216:232] | Density median $x^*$ in bps, the curve's own zero (§5.1) |
| `dispRefPbps` | header[232:248] | Reference dispersion in pbps that the fit was built at |
| `flags` | header[248:256] | bit 0 = `FLAG_REQUIRES_WALL` |
| `c_0..c_4` | `segs[2i]`, `segs[2i+1]` low | Power-basis coefficients on local $u \in [0,1]$, int64, pbps$\cdot Q$ |
| $S_j$ | `segs[2i+1]` high | Exact prefix integral $\int_0^{b_j} y\,dx$, int128, pbps$\cdot Q\cdot x$ |

The directory holds the $m-1$ **interior** boundaries only. The last, $b_m$, is always
$\text{BPS}$: a constant a 14-segment curve would store 14 times. `_frame` takes that edge from the
constant instead, and the freed uint16 carries `median` at zero extra storage, zero extra
SLOADs and an unchanged `MAX_SEGS`. The header still resolves the full directory, so `_frame` locates
a segment with pure word operations.
Header fields are read through named accessors (`NUQuartic.median`, `dispRefOf`, `requiresWall`), not
by hand-written shifts.

### 2.2. Preset table and asset pointer

> Canonical for the codebook rationale. [Parametrization §9.2](/docs/1-1-7-parametrization#92-why-the-table-exists-a-quantized-density-codebook) and the [Overview](/docs/1-overview#8-liquidity-curves) summarise it only.

Curves live in a shared per-pool table `PoolStorage.curves`, `mapping(uint16 => NUQuartic.Curve)`. Each
asset carries `Asset.presetId` (uint16). `presetId = 0` is the explicit no-shape sentinel and is
**refused at config**: `PoolConfig.validatePresetAssign` will not list an asset without a curve, so
every leg that can be quoted carries one and there is no shapeless code path (§9).

**An asset does not own a curve.** It points at one and scales it by
$\text{dispersion}/\text{dispRef}$. The reference roster carries **5 presets across 28 legs**; the live Arc fleet ships four of them (preset 3 unused)
across 26 symbols (reference-roster values; per-parameter tables in [Parametrization](/docs/1-1-7-parametrization#12-reference-parameter-table)). A stable and a volatile
share a preset id when they want the same width; the five presets carry three distinct
`wQ` vectors and are not interchangeable rescalings of one another (§4.3).

**The table is a quantized density codebook.** An asset's depth density is fitted off chain from its
observed tape and then mapped to the **nearest entry already in the table**, rather than written to
chain as its own curve. Two costs drive that design, an order of magnitude apart:

- Writing a curve is expensive and rare. It runs the full validation and segment build, costs the
  gas in §4.5, and is timelocked (`requestOp(..., UPDATE_CURVE, ...)` / `executeSetCurve`, §10).
- Re-pointing an asset is cheap and frequent. `Asset.presetId` is a uint16 pointer, so as an asset's
  observed density drifts the keeper moves it to a different codebook entry through `setProfile`
  without touching any curve.

The price of quantization is the residual between an asset's own fit and its nearest codebook entry.
The continuous $\text{dispersion}/\text{dispRef}$ y-scale (§6.1) absorbs the **scale** part of that
residual exactly, at any value, with no refit. The codebook therefore has to span only
**shape**: how the density is distributed across the depth axis, not how wide it is. Five entries
cover 28 legs because those legs disagree about width far more than about shape.

### 2.3. Validation at install

`NUQuartic._validate` (`NUQuartic.sol`) and `NUQuartic.set` (`NUQuartic.sol`):

| Property | Requirement |
|----------|-------------|
| `dispRefPbps` | $\ne 0$ (it is a divisor in `_scaleY`; zero would brick every quote) |
| `wQ` length $n$ | $5 \le n$, $n - 4 \le 14$ |
| `interior` length | exactly $n - 5$ |
| Non-flat | `wQ[n-1] != wQ[0]` |
| Monotone | `wQ[i] >= wQ[i-1]` for all $i$ |
| Interior knots | strictly increasing, in $(0, \text{BPS})$ |
| Coefficients | $|c_i| \le 2^{63} - 1$, $|S| \le 2^{127}-1$ |

Segment count is $m = n - 4$. The knot vector is clamped degree 4: five copies of 0, the interior
knots, five copies of BPS.

**The stored curve is centered on the mark.** Before building the segments, `set` shifts the whole
polygon by $(wQ[0] + wQ[n-1])/2$ so that $y(0) + y(\text{BPS}) = 0$, i.e. $\beta = y(0)/\text{span}
\equiv -1/2$ (`NUQuartic._centre`, `NUQuartic.sol`). Clamped endpoints make
$y(0) = wQ[0]$ and $y(\text{BPS}) = wQ[n-1]$, so the transform is one subtraction per weight. It is
**shape-preserving** (the fitted density $y'$ is untouched), idempotent, and exact but for the 1
Q-unit an odd sum cannot split, and a no-op on all five shipped presets, whose closed-form fits
are already antisymmetric.

A level bias between the mark and the curve is unpriced
optionality, and admitting one forced the interior leg to re-center a mid the terminal leg did not,
which put two prices on one tree edge ([Anchor Path Pricing §3.1](/docs/1-1-3-anchor-path-pricing#31-one-pricing-law-per-edge)).
Curves are **centered rather than refused** because a fitted control polygon lands within about
0.5 pbps of antisymmetric, so an equality check would reject every real curve over its own fitting
residual. Centering subsumes any admission bound on $\beta$: magnitudes only shrink under the shift,
so the `int64` coefficient bound still gates the shape.

**The density median is computed and stored at the same write.** Centering pins $y(0) = -\text{span}/2$,
so $y$ is the integral of a nonnegative density running from $-\text{span}/2$ to $+\text{span}/2$ and
the single $x^*$ with $y(x^*) = 0$ is the point that splits the density in half. `NUQuartic.set`
writes the segments first, then binary-searches the **stored power-basis curve** through the same
`evalQ` the quote path runs (14 evaluations, bracketed by $y(0) \le 0 \le y(\text{BPS})$), and packs
the result into the header. Storing it rather than root-finding at the read is a hot-path decision:
the swap path may not pay 14 evaluations. Because $x$ is integer bps, $|y(x^*)|$ is bounded by one
x-unit of density rather than by zero; on an antisymmetric curve it is exactly zero and
$x^* = \text{BPS}/2$. This is the value `Pricing._skewToDepth` anchors zero inventory skew on (§5.1).

At **assignment** time `PoolConfig.validatePresetAssign` (`PoolConfig.sol`) adds two checks:

- The preset must carry a real curve: `header == 0` reverts `NotConfigured(ASSET, token)`. `presetId = 0`
  is the empty sentinel and `setCurve` refuses to install a shape there, so a curveless asset is
  unconstructible.
- A preset with `FLAG_REQUIRES_WALL` may only be assigned to an asset with
  `kappaCovBps != 0` (`PoolConfig.sol`, reverts `BadConfig`).

The price multiplier's positivity needs no check of its own: `sanitizeDispersion` holds the asset's
`minDispersionPbps` under `Pricing.dispersionCap`, which is `INTERIOR_SWING_CAP_PBPS` expressed in the
preset's own units, so the deepest offset the shape can quote at that floor is half the swing cap,
5000 against PBPS's 1e6. `MAX_DISPERSION_PBPS = 900000` (90% of PBPS) is the separate hard bound
(`PoolConstantsLib.sol`, clamped in `Pricing._calculateDispersion`).

---

## 3. Spline mathematics

### 3.1. Why an I-spline

An I-spline is the integral of an M-spline, a nonnegative B-spline basis. Writing

$$y(x) = \sum_i w_i B_i(x)$$

with clamped quartic B-splines $B_i$, monotonicity of $y$ is **equivalent** to $w_{i+1} \ge w_i$. That
is a linear constraint on the coefficients, checkable exactly on-chain in $O(n)$ integer comparisons.
No tangent clamps, no per-segment monotonicity proofs, no floating point.

### 3.2. Conversion to power basis

At install, `_segCoeffs` (`NUQuartic.sol`) converts each span to power basis on local
$u \in [0, 1]$:

$$y(u) = c_0 + c_1 u + c_2 u^2 + c_3 u^3 + c_4 u^4$$

$c_0, c_1, c_2$ come from the **left** jet of the span (value, first and second derivative from de
Boor), and $c_3, c_4$ from the right-end value and slope:

$$c_1 = \frac{y'(t_s)\cdot h}{D}, \quad c_2 = \frac{y''(t_s)\cdot h^2}{2D}, \quad
A = y(t_{s+1}) - c_0 - c_1 - c_2, \quad B = \frac{y'(t_{s+1})\cdot h}{D} - c_1 - 2c_2$$

$$c_3 = 4A - B, \qquad c_4 = B - 3A$$

with $h = t_{s+1} - t_s$. $D = 10^6$ (`NUQuartic.sol`) is an extra fixed-point scale carried through
the derivative pyramids: slope truncation feeds $c_2 = s_0 h^2 / 2$, so a 1-unit error in $s_0$ would
cost $h^2/2$ units of $y$, which is $1.25\times10^7$ at a 5000-unit span. $D = 10^6$ caps that residual
at about 13 units of pbps$\cdot Q$, i.e. $1.3\times10^{-8}$ pbps.

Value, slope and $y''$ are continuous across spans, so evaluating the left jet inside a span equals the
previous span's right jet. No carry is needed between spans.

### 3.3. Evaluation

`NUQuartic.evalQ` (`NUQuartic.sol`): locate the segment from the header, load two slots, Horner
on five coefficients.

$$u = \frac{(x - x_0)\cdot P}{h}, \qquad P = 10^{18}$$

**3 cold SLOADs** total (header plus two segment slots). $u$ is clamped to $[0, P]$ by
`if (dx > h) dx = h`.

### 3.4. Exact O(1) integration

VWAP needs $\int_{x_1}^{x_2} y\,dx$. Each segment stores the exact prefix integral to its left edge, so

$$\int_{x_1}^{x_2} y\,dx = S(x_2) - S(x_1)$$

`NUQuartic.areaQ` (`NUQuartic.sol`) returns 0 when $x_1 \ge x_2$, otherwise differences two
`_at` calls. `_at` (`NUQuartic.sol`) is the stored prefix plus the local quintic primitive:

$$S(x) = S_i + \frac{h}{P}\left(c_0 u + \frac{c_1 u^2}{2} + \frac{c_2 u^3}{3} + \frac{c_3 u^4}{4} + \frac{c_4 u^5}{5}\right)$$

Result units are pbps$\cdot Q\cdot x$. **5 cold SLOADs** (header plus two slots for each of the two
boundary segments), and the cost is **independent of how many segments the trade crosses**: the
prefix integrals turn what would be a per-segment sum into one subtraction.

The full-segment integral stored at install is exact in closed form (`NUQuartic.sol`):

$$\int_{\text{seg}} y\,dx = \frac{h\,(60 c_0 + 30 c_1 + 20 c_2 + 15 c_3 + 12 c_4)}{60}$$

---

## 4. Preset: central-normal plateau

**The shipped shape.** Every codebook entry is a central-normal plateau.

### 4.1. Closed form

The signed quote offset is modelled as $N(0, \sigma_g)$ truncated at the empirical **q70**, the exact
30% folded-tail cut. The depth axis $x$ is the CDF of that distribution, so the offset curve is its
**quantile function**:

$$y(x) = H \cdot \frac{\Phi^{-1}\!\left(0.15 + 0.7\,\dfrac{x}{\text{BPS}}\right)}{\Phi^{-1}(0.85)},
\qquad \Phi^{-1}(0.85) \approx 1.03643$$

$H$ is the **half-swing**: half the peak-to-peak offset range, evaluated at the fit's reference
dispersion `dispRefPbps`. Away from the knots the shipped spline reproduces the closed form to better
than 0.005 pbps at the fitted scale; at a knot it is pinned to the control weight instead and the
deviation is larger:

| $x$ (bps) | Closed form, $H = 100$ | Shipped spline (preset 1/2) |
|-----------|------------------------|------------------------------|
| 0 | $-100$ | $-100.0000$ |
| 1314 | $-67.51$ | $-67.6849$ |
| 2500 | $-43.78$ | $-43.7827$ |
| 5000 | $0$ | $0.0000$ |
| 7500 | $+43.78$ | $+43.7827$ |
| 10000 | $+100$ | $+100.0000$ |

(The 1314 row is the knot itself, where the spline is pinned to the control weight rather than to the
target: 0.175 pbps, or 0.175% of $H$.)

The **density** is $dx/dy$, whose reciprocal
$dy/dx = 0.7 H / \left(\varphi(\Phi^{-1}(\cdot))\,\Phi^{-1}(0.85)\right)$ is minimized at $x = 5000$.
Density is therefore maximal at the mark and falls off symmetrically to both edges. Because $\varphi$
is flat near 0, the top is a genuine table-top: not a dome, not a spike. Measured local slopes on the
shipped preset ($H = 100$): 0.0246 pbps/bp on $[0, 1314]$, 0.0202 on $[1314, 2500]$, 0.0185 on
$[2500, 3050]$, 0.0172 on $[3050, 5000]$.

### 4.2. Structure

$m = 3$ segments, two interior knots at $x = \{1314, 8686\}$ bracketing the mark at $\pm 0.7\sigma$.
The middle segment carries the flat top, the two outer segments carry the shoulder roll-off.
$3 \times 2 + 1 = 7$ storage slots.

The **model** behind all five entries is the one closed form of §4.1 evaluated at three half-swings,
and the ratio $H/\text{dispRef}$ is what the live dispersion then scales continuously (§6). That is a
statement about the model, not about the bytes on chain: each vector was fitted and rounded
independently, so the stored weights are not exact multiples of one another. §4.3 gives the residual
and what it is worth at a quote.

### 4.3. Shipped presets (5 rows)

Control weights are $\pm H \cdot Q$ scaled: `wQ[0] = -H_pbps * 1e9`, `wQ[6] = +H_pbps * 1e9`.

| ID | $H$ | `wQ` vector | `dispRefPbps` (pbps) | $H/\text{dispRef}$ | Wall-gated | Seeded from |
|----|-----|-------------|------------------|---------------------|------------|-------------|
| 1 | 1 bp = 100 pbps | A | 100 | 1 | No | USDC |
| 2 | 1 bp = 100 pbps | A | 100 | 1 | Yes | USDT |
| 3 | 2 bp = 200 pbps | B | 100 | 2 | Yes | USDE |
| 4 | 5 bp = 500 pbps | C | 100 | 5 | Yes | USDG |
| 5 | 5 bp = 500 pbps | C | 500 | 1 | No | WETH |

All five share `interiorB = [1314, 8686]` and a 7-element weight vector. The three distinct vectors,
in pbps$\cdot Q$:

```text
A = [-100000000000,  -90053719349,  -38039704575, 0,  38039704575,  90053719349, 100000000000]
B = [-200000000000, -180107438698,  -76079409151, 0,  76079409151, 180107438698, 200000000000]
C = [-500000000000, -450268596744, -190198522877, 0, 190198522877, 450268596744, 500000000000]
```

**Three distinct `wQ` vectors, not one shape scaled five ways.** Presets 1 and 2 are byte-identical in
shape and differ only in `FLAG_REQUIRES_WALL`; presets 4 and 5 are byte-identical in shape and differ
only in `dispRefPbps` (100 against 500). The three vectors that remain are **not** exact multiples of each
other, because each is an independently-rounded fit of the §4.1 closed form:

| Relation | Exact multiple of A | Stored | Residual |
|---|---:|---:|---:|
| $2\times$ `A[2]` against `B[2]` | $-76{,}079{,}409{,}150$ | $-76{,}079{,}409{,}151$ | 1 ULP |
| $5\times$ `A[1]` against `C[1]` | $-450{,}268{,}596{,}745$ | $-450{,}268{,}596{,}744$ | 1 ULP |
| $5\times$ `A[2]` against `C[2]` | $-190{,}198{,}522{,}875$ | $-190{,}198{,}522{,}877$ | 2 ULP |

One ULP is $10^{-9}$ pbps at the fitted scale. `_scaleY` multiplies by $\kappa/\text{dispRef}$ before
truncating to whole pbps, and that amplification is bounded by `dispersionCap` at 10x (§6.5), so the
residual reaches at most $2\times10^{-8}$ pbps against a 1 pbps quote granularity: **it does not on
its own move a quoted mid.**

It matters for a different reason. The shipped vectors are canonical on-chain state, so a vector
regenerated by rescaling another will not reproduce the shipped bytes and will fail parity against
on-chain state and against the research parity vectors. Treat the presets as five independent curves:

- Do not derive one from another.
- Do not assume a quote on preset 3 equals twice a quote on preset 1.
- **Re-fit and compare against the shipped values rather than rescale** when refreshing an entry.

**The ratio $H/\text{dispRef}$ is what sets a preset's width at quote time.** The live half-swing of
the book is

$$\tilde y(\text{BPS}) = \frac{H}{\rho} \cdot \kappa$$

so with dispersion $\kappa$ in pbps, presets 1, 2 and 5 reach exactly $\kappa$ pbps at the edge,
preset 3 reaches $2\kappa$, preset 4 reaches $5\kappa$.

### 4.4. Quiet-tape half-swings

Using deployed `minDispersionPbps`. These are the quiet-tape ($\sigma = 0$) widths; under the
adaptive-dispersion law every leg quotes above its floor whenever $\sigma > 0$, one-for-one at
shipped vega (§6.4):

| Asset | Preset | $H/\text{dispRef}$ | `minDispersionPbps` | Quiet-tape edge $\pm$ |
|-------|--------|--------------------|-----------------|------------------|
| USDC | 1 | 1 | 200 | 200 pbps = 2.00 bp |
| USDT | 2 | 1 | 161 | 161 pbps = 1.61 bp |
| USDG | 4 | 5 | 73 | 365 pbps = 3.65 bp |
| DAI | 3 | 2 | 559 | 1118 pbps = 11.18 bp |
| USDTB | 2 | 1 | 470 | 470 pbps = 4.70 bp |
| WETH | 5 | 1 | 2034 | 2034 pbps = 20.34 bp |
| XAUT | 5 | 1 | 1500 | 1500 pbps = 15.00 bp (was 3601; cut 2026-09-04 with the metal class) |
| KRW1 | 5 | 1 | 3740 | 3740 pbps = 37.40 bp |

### 4.5. Gas

Harness: `NUQuarticSetGas.t.sol`, measuring cold and warm `NUQuartic.set` by segment count and
by shipped preset. The figures below are recorded run values, not assertions in the benchmark, so
re-measure before quoting them externally.

| Shape | $m$ | Slots | `setCurve` update (warm) | First set (cold) |
|-------|-----|-------|--------------------------|------------------|
| Central-normal plateau (shipped) | 3 | 7 | ~289,800 | ~409,500 |

Cost is linear in segment count: two storage slots per segment plus the header, written once and read
three or five times per quote (§11.1). At $m = 3$ every codebook entry sits at the cheapest end of
that scale, making a write-once, point-often codebook (§2.2) cheap to maintain.

---

## 5. Inventory skew mapping (skew -> depth)

> For how $\psi$ is computed from coverage, see
> [Inventory Management §3](/docs/1-1-1-inventory-management#3-inventory-skew-coverage--skew).

### 5.1. The Center

`Pricing._skewToDepth`:

$$x_0 = \mathrm{clamp}\!\left(x^* + \psi\cdot\frac{\text{BPS}}{200},\ 0,\ \text{BPS}\right)
= \mathrm{clamp}(x^* + 50\,\psi,\ 0,\ 10000)$$

$x^*$ is the curve's own **density median**, read from the header (§2.1, §2.3). It is the one $x$ at
which the curve quotes the mark itself, so zero inventory skew quotes the mark for every shape.

| $\psi$ | $x_0$ (antisymmetric preset, $x^* = 5000$) | Meaning |
|--------|-------|---------|
| $-100$ | 0 | Maximum discount, pool is over-covered and wants to sell |
| $0$ | $x^*$ = 5000 | Center, quote at the mark |
| $+100$ | 10000 | Maximum premium, pool is under-covered and wants to buy |

**One definition of center, held on both sides of the write.** `_centre` pins
$y(0) = -\text{span}/2$ at the **write**, which puts the density median on the mark, and
`_skewToDepth` starts the traverse from that same median at the **read**. The two agree by
construction on any shape. The domain midpoint $\text{BPS}/2$ is not the anchor and only coincides
with it on an antisymmetric $wQ$: all five shipped presets are antisymmetric on symmetric knots, so $x^* = 5000$ exactly on each, but anchoring on the midpoint would quote $y(\text{BPS}/2) \ne 0$
at balanced coverage on any asymmetric shape, up to half the peak-to-peak swing, and no read path
would correct it. On a shape pinned at the widest dispersion its own fence admits that is **42 bps
off mark at zero inventory skew**, 4.2 bps at `dispRefPbps`.

**The anchor is an offset, not a re-scaling, and the slope is not free.** $\text{BPS}/200$ per skew unit
is exactly what round-trip conservation admits, on both arms: with $k$ skew units per unit of coverage
and $s$ x-units per skew unit, the state advance must stay under the traverse's, $k\cdot s \le
\text{BPS}/c$. The draining arm ($k = 200$) binds at $c = 1$ and the filling arm ($k = 100$) at
$c \to 2$, and both give $s \le \text{BPS}/200$. A piecewise map onto $[0, x^*]$ and $[x^*, \text{BPS}]$
would have slopes $(\text{BPS}-x^*)/100$ and $x^*/100$, which satisfy both bounds only at
$x^* = \text{BPS}/2$; anywhere else one arm out-steps the traverse and a ping-pong trader harvests the
gap. Moving the anchor at the **unchanged** slope moves a level, which cancels exactly on a closed
loop, and the domain clamp only ever shortens a step. `computeInventorySkew` is untouched
([Inventory Management §3](/docs/1-1-1-inventory-management#3-inventory-skew-coverage--skew)).

The addition is `unchecked`. That is sound because $x^* \le \text{BPS}$ and `computeInventorySkew`
clamps $\psi$ to $[-100, +100]$, bounding the sum to $[-5000, 15000]$. The **clamp**, not the caller,
is what returns it to $[0, \text{BPS}]$: a $\pm 100$ skew bound alone keeps $x_0$ in range only when
$x^* = \text{BPS}/2$, and $x^*$ is a per-curve quantity, so the clamp is load-bearing rather than
defensive. Any new caller must still pass a clamped skew.

### 5.2. The book is asymmetric before anything else acts

$x_0$ splits the tradeable domain $[0, \text{BPS}]$ into a sell side of width $x_0$ and a buy side of
width $\text{BPS} - x_0$. At the reference skews (Inventory Management §3.3):

| Asset | $\psi$ | $x_0$ | Sell-side width | Buy-side width |
|-------|--------|-------|-----------------|----------------|
| USDC | $-32$ | 3400 | 3400 bp | 6600 bp |
| USDT | $-19$ | 4050 | 4050 bp | 5950 bp |
| DAI | $-5$ | 4750 | 4750 bp | 5250 bp |
| RLUSD | $+7$ | 5350 | 5350 bp | 4650 bp |
| USDG | $+9$ | 5450 | 5450 bp | 4550 bp |

(All five legs sit on antisymmetric presets, so $x^* = 5000$ and $x_0 = 5000 + 50\psi$ on these rows.
On an asymmetric preset the same table would read $x^* + 50\psi$.)

This asymmetry exists **before** any reserve cap, before the coverage toll, and before the fee. It is
the intended behavior: an over-covered leg has little room left to be sold into and a lot of room to
be bought out of.

### 5.3. Aggregating virtual depth across pools

> Off-chain view only. `@btr-protocol/sdk` (`aggregateDepthCurves`), consumed by the swap
> depth panel and the chart liquidity bands. The chain quotes one pool at a time; this is the
> venue-level book a router faces.

A pair can be quoted by more than one pool. USDT/USDC is quoted by both the stable pool and the
volatile pool. Each pool carries its own coverage, hence its own $\psi$, hence its own center
$x_0 = x^* + 50\psi$ (§5.1), hence its own skew mid $m_p = \text{priceAt}(x_0)$. **Two pools on the
same pair do not share a mid**, and the gap between their mids is the inter-pool arbitrage, not a
spread.

Per pool $p$ the aggregator reads the quoted depth curve and keeps six quantities: the skew touch
$b_p$ / $a_p$ (pre-fee, pre-toll, `curve.bids[0].price` / `curve.asks[0].price`), the same touch net
of fee and coverage toll $\tilde b_p$ / $\tilde a_p$, the mid $m_p$, the mark, the spread, and the
densified ladder. The weight is the pool's total quoted size, $w_p = \sum \text{sizes}$.

**The touch is an extremum, not a mean.** A taker routes to one pool, the best one:

$$b = \max_{p \in \mathcal{B}} b_p, \qquad a = \min_{p \in \mathcal{A}} a_p$$

and identically on the net touch, $\tilde b = \max \tilde b_p$, $\tilde a = \min \tilde a_p$. The
index sets $\mathcal{B}$, $\mathcal{A}$ hold only the pools actually quoting that side, so a
one-sided pool (reserve-clipped, §7) cannot drag the side it does not quote. A size-weighted touch,
$\sum b_p w_p / \sum w_p$, prints a bid below the best bid and an ask above the best ask: a price no
router accepts and no fill reaches. On a two-pool pair whose mids differ by $\delta$, the weighted
bid understates the executable bid by up to $\delta$.

**Size-weighting stays on the ladder and on the scalars.** Behind the touch, sizes are additive:
rungs from different pools that fall in the same price bucket are summed, $S(\pi) = \sum_p S_p(\pi)$,
because a taker sweeping to price $\pi$ takes both. The venue mid, mark and spread are depth-weighted
means, $\bar m = \sum m_p w_p / \sum w_p$: they are ladder-centering and reference quantities, not
executable prices. Mid answers "where is the book centered, and what is the inventory premium against
the mark", which is a venue-wide average; touch answers "what do I fill at", which is a max.

**Invariant: $b \le \bar m \le a$**, with an empty side not binding. The weighted mean is clamped
into the touch interval. The clamp binds only when the touch set differs from the mid set, which a
one-sided pool causes: a heavy ask-only pool at $m_p = 2.0$ next to a two-sided pool at $1.0$ leaves
$a = 1.0$ while the raw mean is $1.9$. The invariant is what keeps the taker cost split (net touch
measured from mid) signed correctly on both sides.

**Pre-fee the aggregated touch can cross.** Pre-fee each pool's two sides meet at its own mid,
$b_p = a_p = m_p$, so with distinct mids $b = \max m_p > \min m_p = a$. That crossing is real: it is
the cross-pool arbitrage, worth $b - a$ per unit before costs. It is not a spread and is not printed
as one. The executable statement is the net touch $\tilde b$, $\tilde a$, which crosses only when the
mid dispersion exceeds the two crossing costs. Consumers gate on $\tilde a > \tilde b$ before drawing
a cost band.

**The ladder is priced on the NET basis.** Each rung takes its own $\tilde\pi$, the executable price
at that rung's own size (half the path spread plus the coverage toll evaluated there), not the skew
price $\pi$. Where a toll binds this widens the ladder with depth rather than shifting it by a
constant. The skew touch $b$, $a$ survives as a reference (the inventory premium against the mark,
and grossing net sizes up); nothing draws it. Sizes stay gross: $\text{cum}\cdot m$ is not monotone
into the coverage wall, so netting them would truncate the ladder exactly where depth matters.

One basis on the price axis is the point. The drawn quote is $\tilde b$, $\tilde a$; a ladder priced
on $\pi$ underneath it sits INSIDE that quote, and within a single pool, where $b_p = a_p = m_p$,
every rung lands between the drawn bid and the drawn ask. Measured on live AUDF/KRW1: 30 of 30 rungs
inside the touch.

**No rung is priced through the touch.** Bucketing opens each pool's ladder strictly beyond that
pool's own net touch (bid buckets floor below $\tilde b_p$, ask buckets ceil above $\tilde a_p$), so
every merged bid rung is below $\tilde b_p \le \tilde b$ and every merged ask rung above
$\tilde a_p \ge \tilde a$. The max/min touch is what makes this hold: under a weighted mean the
tightest pool's rungs sit through the printed touch, showing size available at a price better than
the best price.

**The step resolves the ladder, not its distance from mid.** The bucket width is chosen from each
side's rung EXTENT, touch to far end, taken per pool. Measuring it from $\bar m$ instead folds in the
gap between mid and the touch, which is zero only on the skew basis: on the net basis it pushed the
step about $5\times$ too coarse and collapsed a 15-rung side to 4.

---

## 6. Dispersion dynamics

### 6.1. The dispersion contract

Each preset is fitted at a reference dispersion `dispRefPbps` stored in its header. A live quote scales the
offset linearly (`Pricing._scaleY`, `Pricing.sol`):

$$\tilde y = \left\lfloor \frac{y_Q \cdot \kappa}{\rho\,Q} \right\rfloor_{\to 0}$$

where $y_Q$ is the stored pbps$\cdot Q$ value, $\kappa$ the live dispersion in pbps, $\rho$ the curve's
`dispRefPbps`, and $Q = 10^9$.

A linear y-scale preserves monotonicity and C2 **exactly**, so volatility can widen or tighten the
curve with no refit. The preset fixes the shape and its reference half-swing; dispersion is the
continuous scale applied to that shape at every quote.

**Truncation direction matters here.** Solidity integer division truncates toward zero, so a **negative**
offset rounds **up** (toward zero, i.e. toward the mark) and a positive offset rounds down (also toward
the mark). The scaled offset is therefore always at least as close to the mark as the exact value, by
at most 1 pbps. That is pool-unfavorable on the sell side by under 0.0001%, and it is the reason the
economic floors in §9 exist as an independent backstop rather than relying on the scale.

### 6.2. Live dispersion formula

> Canonical. Every other statement of $\kappa$ in the docs points here rather than restating the law.

`Pricing._calculateDispersion` (`Pricing.sol`):

$\kappa = \min\!\left(\texttt{MAX\_DISPERSION\_PBPS},\ \kappa_{\min} + \left\lfloor \frac{\sigma\cdot\nu}{\text{BPS}} \right\rfloor\right)$
(`Pricing.sol`; since the 2026-08-21 adaptive-dispersion change, which dropped a historic
$\sigma/1000$ damping that pinned every book at its quiet-tape floor.)

| Symbol | Field | Type | Unit |
|--------|-------|------|------|
| $\kappa$ | return value | uint32 | pbps |
| $\kappa_{\min}$ | `Asset.minDispersionPbps` | uint32 | pbps, the quiet-tape floor |
| ceiling | `MAX_DISPERSION_PBPS` | uint32 | pbps **protocol constant** = 900000 (`PoolConstantsLib.sol`). There is no per-asset ceiling field; `Asset.maxDispersion` does not exist |
| $\sigma$ | `FeedData.sigmaPbps` | uint32 | PBPS (1e6 = 100%, so 10000 = 1%) |
| $\nu$ | `Asset.vegaBps` | uint16 | basis 10000 |

The divisor is `SC.BPS`. At $\nu = 10000$ the slope is exactly one: dispersion tracks the feed's σ
1:1 above the floor. Live vega is 1.0x on stable legs and on every pool's hub, and below it on the
volatile classes — 0.30x FX, 0.40x crypto majors, 0.45x metals, 0.35x equities — so on those the
slope is that fraction of σ ([Parametrization §4.2](/docs/1-1-7-parametrization#42-vega-volatility-sensitivity)).

$\kappa_{\min}$ is the **additive base**, not a clamp applied after the fact: at $\sigma = 0$ the curve
sits exactly at $\kappa_{\min}$. Only the ceiling is a clamp. There is deliberately no fixed base
dispersion: a hardcoded base would make tight stable bands of 1 to 6 bp unreachable in a quiet tape.

### 6.3. Volatility sensitivity

$\Delta\kappa = \left\lfloor \frac{\sigma\nu}{\text{BPS}} \right\rfloor$

| $\sigma$ (PBPS) | $\sigma$ as % | $\nu$ | $\Delta\kappa$ (pbps) |
|-----------------|---------------|-------|------------------------|
| 76 | 0.0076% | 10000 | 76 |
| 999 | 0.0999% | 10000 | 999 |
| 5000 | 0.5% | 10000 | 5000 |
| 10000 | 1% | 10000 | 10000 |
| 50000 | 5% | 10000 | clamped at `MAX_DISPERSION_PBPS` − $\kappa_{\min}$ |
| 10000 | 1% | 5000 | 5000 |

### 6.4. Sigma is the live width driver at shipped vega

**Deployed $\nu = 10000$ on stable legs and on every pool's hub**; the volatile classes were cut
to 3,000-4,500 on 2026-09-04 to keep their books inside the interior swing cap (§6.5). At
$\nu = 10000$ the formula collapses to
$\Delta\kappa = \lfloor \sigma \rfloor$ pbps: dispersion tracks the feed's σ one for one above
the floor. Consequences:

- Every 1% of feed σ adds 10000 pbps - a full 1% of mark - to the leg's band, on top of
  `minDispersionPbps`.
- Live USDT ($\sigma = 76$ PBPS on oracle `0xd3fb...f0e4`, feed `0xe2ca...aef9`; floor 161): the band is
  $161 + 76 = 237$ pbps, not the bare floor.
- WETH (floor 2034, $\nu = 4000$): a 1% $\sigma$ adds 4000 pbps, taking the quiet-tape band to 6034. At the pre-cut $\nu = 10000$ the same σ took it to 12034 — over the preset's swing cap, which is why the class was cut.
- The only ceiling is `MAX_DISPERSION_PBPS` (§6.2), reached at $\sigma \approx 89.8\%$ at
  $\nu = 10000$. What a leg's *shape* tolerates is governed by §6.5 below.

**Both the floor and the slope are shaping parameters.** `minDispersionPbps` sets the quiet-tape
width; $\nu$ sets how fast σ widens it. A model or simulation that treats dispersion as purely
floor-pinned describes the pre-adaptive-dispersion contract, not this one.

$\sigma$ also drives the volatility term of the path spread and the staleness surcharge. See
[Spread & Fees](/docs/1-1-4-spread-fees).

### 6.5. Parameters

| Parameter | Type | Unit | Purpose |
|-----------|------|------|---------|
| `vegaBps` | uint16 | basis 10000 | Volatility sensitivity; 10000 everywhere deployed |
| `minDispersionPbps` | uint32 | pbps | Additive base and quiet-tape floor; bound at the write under the preset's fence cap |
| `MAX_DISPERSION_PBPS` | uint32 | pbps | Protocol-wide read ceiling (§6.2). Not a parameter and not per-asset |
| `dispRefPbps` | uint16 | pbps | Reference dispersion of the fit, in the curve header |

**The preset cap bounds the write path, the protocol constant bounds the quote.**
`Pricing.dispersionCap` returns $\text{cap}\cdot\text{dispRef}\cdot Q/\text{span}$ with
$\text{cap} = \texttt{INTERIOR\_SWING\_CAP\_PBPS} = 10{,}000$ PBPS, the widest dispersion whose
interior mid swing still fits the manipulation fence
([Anchor Path Pricing §3.2](/docs/1-1-3-anchor-path-pricing#32-the-interior-fence)).
`PoolConfig.sanitizeDispersion(minDispersionPbps, cap)` (`PoolConfig.sol`) checks,
never clamps: it maps `minDispersionPbps == 0` to the protocol default of 1000 pbps and
**reverts** `Err.InvalidInput` on any floor above the cap or above `MAX_DISPERSION_PBPS`, at
both write paths (`initAsset` and `setProfile`) - a rejected write rather than a silently
narrowed quiet-tape quote. The σ-driven term above the
floor is **not** capped by the preset: κ rides up to `MAX_DISPERSION_PBPS`, and if it ever exceeds
the shape's own `dispersionCap` the interior swing reverts fail-closed (`Err.Overflow`) - a
σ-triggered outage on that leg, never a mispriced quote. Shipped caps, by the shape's
span-to-`dispRefPbps` ratio:

| Preset | Ratio $\text{span}/(Q\cdot\text{dispRef})$ | `dispersionCap` (PBPS) |
|---|---:|---:|
| 1, 2, 5 | 2 | 5000 |
| 3 | 4 | 2500 |
| 4 | 10 | 1000 |

At $\nu = 10000$ (1x) the swing cap sits $\text{cap} - \kappa_{\min}$ PBPS of σ above
the floor - about 0.48% of added σ on a ratio-2 stable (floor 161, cap 5000) and about 0.09% on a
ratio-10 preset; a class running $\nu$ below 1x buys headroom in exactly that ratio, which is the
live configuration on every volatile class - so a violent tape, not an operator, is what can push a leg into the fail-closed
revert. Past the cap the $\sigma$, CI and staleness terms of the spread keep widening regardless. `setCurve`'s "may not deepen a live preset" rule is exactly $\text{span}/\text{dispRef}$
non-increasing on a centered curve, so the cap is non-decreasing across any legal refit.

---

## 7. The depth denominator

> Full statement: [Inventory Management §4](/docs/1-1-1-inventory-management#4-the-depth-denominator).

$D$ is the leg's **raw reserves**, with a division guard and nothing else:

```solidity
uint256 depth = reserves == 0 ? 1 : uint256(reserves);
```

It is measured in the profile asset's own token units. That is what makes the traversal anchor-free:
`volumeFraction = amountIn * BPS / depth` compares two quantities in the same token, so no price
enters the x-axis at all.

| Reserves | $D$ |
|----------|-----|
| $R = 0$ | 1 wei (division guard) |
| $R > 0$ | $R$, at any coverage |

**Nothing on the depth axis is coverage-dependent.** $D = R$ at every coverage, so the traversed
interval, and therefore the impact charged, scales with $1/R$ and with nothing else. Coverage reaches
the quote through the skew anchor $x_0$ (§5) and the convex coverage toll, both of which charge the
drain. Why inflating $D$ as coverage falls is an LP leak:
[Inventory Management §4](/docs/1-1-1-inventory-management#4-the-depth-denominator).

---

## 8. Traversal

### 8.1. Sell leg

`Pricing._traverseCurve` (`Pricing.sol`), `selling = true` (the profile asset is being sold
into its parent):

$$v_f = \min\!\left(\text{BPS},\ \left\lfloor \frac{q\cdot\text{BPS}}{D} \right\rfloor\right),
\qquad x_1 = \max(0,\ x_0 - v_f)$$

with $q$ the input amount in the profile asset's own units.

and the traversal integrates $[x_1, x_0]$.

### 8.2. Buy leg

`selling = false` (the parent is being sold to acquire the profile asset):

$$x_1 = \min(\text{BPS},\ x_0 + v_f)$$

integrating $[x_0, x_1]$. The buy leg needs $v_f$ in **child** units while `amountIn` arrives in parent
units, so `_priceEdgeHop` (`Pricing.sol`) sizes it in two steps: estimate the child amount at
the zero-volume mid, shift decimals, then run the real traverse on that estimate, then invert:

```text
midPrice  = _legMid(mark, dispersion, curve, skew)      // zero-volume, at x0
estOut    = amountIn * WAD / midPrice
estChild  = estOut shifted by (child.decimals - parent.decimals)
execPrice = _traverseCurve(mark, dispersion, curve, header, x0, estChild, depth, selling=false, midPrice)
amountOut = amountIn * WAD / execPrice
```

The sizing is an estimate at the mid, so the buy leg's realized $v_f$ is correct to first order in
trade size, not exactly. The sell leg has no such step: `amountIn` is already in profile-asset units.

### 8.3. Why round trips lose

The curve is nondecreasing, and the sell branch always integrates the interval **below** $x_0$ while
the buy branch always integrates the interval **above** it. Therefore, for every size and every curve:

$$\bar{y}_{\text{sell}} \le y(x_0) \le \bar{y}_{\text{buy}}$$

A sell executes at or below the mid, a buy at or above it, and the gap is monotone in size. There is no
size, no skew and no dispersion at which a round trip through one leg gains. Combined with monotonicity
(condition C2) and the single-canonical-integer rule for edge marks (condition C1), this extends to any
closed cycle in the anchor tree. See [Anchor Path Pricing](/docs/1-1-3-anchor-path-pricing).

### 8.4. The volume-fraction quantum

$v_f$ is an integer number of bps, so **a trade smaller than $D/\text{BPS}$ has exactly zero price
impact**: $v_f = 0$, `width == 0`, and the traversal returns the point value at $x_0$
(`Pricing.sol`).

Live USDT ($D = R = 60{,}035.65$): the quantum is 6.0036 USDT. A 1 USDT swap traverses nothing and
executes exactly at the mid. This is confirmed on-chain in §8.6. It is not a rounding artifact worth
removing: the quantum is 0.01% of depth by construction, and any curve impact below it is smaller than
1 pbps at the shipped wall widths.

### 8.5. Average execution price

$$\bar{p} = m\cdot\frac{\text{PBPS} + \bar{y}}{\text{PBPS}},
\qquad
\bar{y} = \left\lfloor \frac{\left\lfloor \dfrac{A(x_1, x_2)}{x_2 - x_1} \right\rfloor \cdot \kappa}{\rho\,Q} \right\rfloor$$

with $A(x_1,x_2) = \int_{x_1}^{x_2} y\,dx$ from `NUQuartic.areaQ`.

`Pricing.sol`. The division by width happens **before** `_scaleY`, in pbps$\cdot Q$ units, so
the intermediate keeps 9 decimal digits of headroom and the only meaningful truncation is the final
one, to whole pbps.

Units: `areaQ` returns pbps$\cdot Q\cdot x$; dividing by the width in $x$ gives
pbps$\cdot Q$; `_scaleY` divides by $Q$ and rescales by $\kappa/\text{dispRef}$ to give pbps; the price
formula divides by PBPS. Skipping any one of those three is a factor of $10^9$, $\kappa/\text{dispRef}$
or $10^6$ respectively.

### 8.6. Worked example, verified on chain

Stable pool, USDT to USDC (upward leg, USDT sells into its base anchor). Inputs, all read live at capture time:

| Input | Value | Source |
|-------|-------|--------|
| $R_{\text{USDT}} = D$ | 60,035.652 | `getAsset`, $c > 1$ so $D = R$ |
| $\psi$ | $-19$ | `SwapQuote.skewIn` (Inventory Management §3.3 snapshot) |
| $x_0$ | 4050 | $x^* + 50\psi$, $x^* = 5000$ |
| preset | 2 ($W/\text{dispRef} = 1$) | shipped deployment parameters ([2. Deployments](/docs/2-overview)) |
| $\sigma$ | 76 PBPS | oracle feed |
| $\kappa$ | 237 pbps | $161 + \lfloor 76\cdot\nu/\text{BPS}\rfloor = 161 + 76$ at USDT's $\nu = 10000$ |
| mark | 0.9993376994 | `SwapQuote.markPrice` |

The measured column below was captured on chain **before** the adaptive-dispersion change dropped the historic
$\sigma/1000$ damping: at capture time the band sat at the bare floor ($\kappa = 161$, read as
$161 + \lfloor 76/1000 \rfloor$) and the read skew was the old symmetric-slope value ($\psi=-39$,
$x_0=3050$). Under the current law the same live inputs give $\kappa = 237$ and $x_0 = 4050$
($\psi = -19$), so these rows verify the traversal pipeline against the pre-adaptive-dispersion contract;
they are kept as the historical record, not as today's quote.

Predicted at the capture-time inputs ($\kappa = 161$, $x_0 = 3050$), compared to `Pool.getSwapQuote`
on the pre-adaptive-dispersion deployment:

| `amountIn` | $v_f$ (bps) | interval | predicted $\bar{y}$ | predicted $\Gamma - 1$ | measured $\Gamma - 1$ |
|------------|-------------|----------|---------------------|-------------------------|------------------------|
| 1 | 0 | point at 3050 | $-54.09$ pbps | 0 | **0.0 pbps** |
| 1,000 | 166 | $[2884, 3050]$ | $-56.52$ pbps | $-2.43$ pbps | **$-2.0$ pbps** |
| 10,000 | 1665 | $[1385, 3050]$ | $-79.59$ pbps | $-25.50$ pbps | **$-25.0$ pbps** |

Re-predicted at HEAD inputs ($\kappa = 237$, $x_0 = 4050$, same live $R$ and $\sigma$) with the
bit-exact integer evaluation of the stored preset (the Rust reference implementation's
`eval_q`/`area_q`,
the same math `Pricing.sol` runs):

| `amountIn` | $v_f$ (bps) | interval | predicted $\bar{y}$ | predicted $\Gamma - 1$ |
|------------|-------------|----------|---------------------|-------------------------|
| 1 | 0 | point at 4050 | $-38$ pbps | 0 |
| 1,000 | 166 | $[3884, 4050]$ | $-41$ pbps | $-3$ pbps |
| 10,000 | 1665 | $[2385, 4050]$ | $-72$ pbps | $-34$ pbps |

where $\Gamma = \bar{p}/\text{mid}$ is the traversal factor and the mid is the zero-volume price at
$x_0$. In the historical rows the predicted mid offset of $-54.09$ pbps sat against a measured
`midPrice/markPrice` of $-54.00$ pbps. The residuals are the integer truncations in `_scaleY` and in
the area division, each under 1 pbps, as expected; both prices are exact WAD, so none of the residual
is an encoding artifact.

### 8.7. Pipeline

```text
1. Read oracle feed         -> mark, sigma            (Pricing._readOracle)
2. kappa = min(maxDisp, minDisp + sigma*vega/BPS)     (_calculateDispersion)
3. psi   = f(reserves, liabilities)                   (computeInventorySkew, fixed law)
4. D     = reserves == 0 ? 1 : reserves               (inline)
5. x0    = clamp(median + 50*psi, 0, BPS)             (_skewToDepth; median from header)
6. vf    = min(BPS, amountIn*BPS/D)
7. [lo,hi] = selling ? [x0-vf, x0] : [x0, x0+vf], clamped to [0, BPS]
8. curve = PoolStorage.curves[asset.presetId]
9. yBar  = _scaleY(areaQ(lo,hi)/(hi-lo), header, kappa)
10. p    = mark*(PBPS + yBar)/PBPS, floored (section 9)
```

### 8.8. Implementation signature

```solidity
function _traverseCurve(
    uint256 mark,
    uint32 dispersionPbps,
    NUQ.Curve storage curve,
    uint256 header,
    uint256 startDepthBps,
    uint256 amountIn,
    uint256 depth,
    bool selling,
    uint256 buyMidHint      // reuse of the sizing mid on the zero-width buy path; 0 = none
) internal view returns (uint256 avgPrice)
```

`Pricing.sol`.

---

## 9. The price floor

Every price path shares one economic backstop, applied in `Pricing._flooredOffsetPrice`:

| Backstop | Value | Meaning |
|----------|-------|---------|
| `SPLINE_MIN_OFFSET_PBPS` | $-0.9\cdot\text{PBPS}$ | Averaged offset never discounts below $-90\%$, i.e. price never falls below 10% of the mark |

`_flooredOffsetPrice` runs on the area path, the zero-width path and the buy-mid path alike, so a
mutated or degenerate curve cannot quote below 10% of the mark on any route. It is `internal` rather
than `private` so `test_flooredOffsetPrice_*` pins the clamp directly rather than through a curve
fixture, and `test_flooredOffsetPrice_clamps_at_ten_pct_mark` asserts the value.

### 9.1. One pricing law

The traversal has **one pricing law**, and it is the only one an edge can be priced under, because
there is no state in which a listed asset lacks a curve: `PoolConfig.validatePresetAssign` refuses the
listing (§2.2), so every leg that can be quoted carries a preset from its first block.

That is a security property, not a convenience. A second pricing law reachable on any edge is the
exact defect the interior fence exists to prevent
([Anchor Path Pricing §3.1](/docs/1-1-3-anchor-path-pricing#31-one-pricing-law-per-edge)): an
edge terminal on one route and interior on another, priced two ways, is an atomic cycle with no
manipulation and no risk. Refusing the listing closes it by construction rather than by care.

The same argument covers `span`. A clamped degree-4 knot vector makes `evalQ` exact at both ends, so
`span > 0` follows from `_validate` with no rounding hypothesis
(`testFuzz_rangeQ_is_exact_at_both_clamped_ends`) and needs no guard branch. A guard there would be a
branch no mutation could kill, and one returning a zero swing would leave an interior leg carrying no
fence at all.

---

## 10. Governance

Two timelocked levers, both LOW tier, both guardian-cancellable:

1. **Shared curve install or refit.**
   `Admin.requestOp(pool, uint8(IPool.OpType.UPDATE_CURVE), bytes32(uint256(presetId)), abi.encode(interior, wQ, dispRefPbps, flags))` then `executeSetCurve(pool, presetId)`.
   Mutating a preset that live assets reference **is** the periodic-refit path. Full validation
   (monotone weights, knot ordering, segment cap, coefficient bounds) runs at execute.
   `FLAG_REQUIRES_WALL` cannot be flipped on an in-use preset: that would strand referencing assets
   (`PoolConfig.sol`).
2. **Asset repoint.**
   `Admin.requestOp(pool, uint8(IPool.OpType.UPDATE_PROFILE), bytes32(uint256(uint160(token))), payload)` then
   `executeUpdateProfile`. Pricing-shape only: reserves, liabilities and coverage are untouched. The
   wall gate and curve existence are checked at execute (`PoolConfig.validatePresetAssign`).

Stripping $\kappa_{\text{cov}}$ from an asset holding a wall-gated preset is rejected at the risk-config
write (`PoolConfig.sol`), so the gate cannot be evaded from the other side.

**Bootstrap:** before sealing, `Admin.setCurve` and `addAsset` install curves directly.
`sealBootstrap(pool)` permanently closes that path, after which only the timelocked route exists.

---

## 11. Gas

### 11.1. Curve operations (cold, measured)

| Operation | SLOADs | Gas |
|-----------|--------|-----|
| `evalQ` | 3 | ~5.4k |
| `areaQ` over any range | 5 | ~11.2k |

Integration cost is flat in trade size **and** in segments crossed.

### 11.2. Swap, end to end (cold)

| Route | Gas |
|-------|-----|
| base to spoke | ~168.9k |
| spoke to base | ~168.5k |
| spoke to spoke | ~198.5k |

---

## 12. Concentration versus other DEXs

Every incumbent AMM hard-codes one liquidity density through its invariant. AIMM carries no invariant:
the on-chain object is $\text{offset}(\text{depth})$, the integral of a chosen monotone C2 density, so
the density is a design input rather than a consequence of a formula.

### 12.1. Resolution

- **Offset resolution.** AIMM prices offsets in pbps: 1 pbps = 0.0001% = 0.01 bp. Uniswap v3/v4
  quantize to ticks; the finest is 1 bp (`tickSpacing` 1 on the 0.01% tier). AIMM offset resolution is
  about 100x finer, and the half-swing $H$ is continuous and can sit below 0.5 bp, where no v3 position
  can exist. The live USDT half-swing of 1.61 bp is not representable as a single v3 tick.
- **Within-tick shape.** Inside a v3 tick, density is uniform: a flat slab. AIMM density is a smooth
  flat-topped quartic table.

### 12.2. Density model comparison

| DEX class | Density model | Concentration control | Tails |
|-----------|---------------|-----------------------|-------|
| Constant product $xy = k$ (Uniswap v2, Balancer weighted) | One fixed hyperbola set by the invariant | None | Unbounded, to 0 and $\infty$ |
| Curve StableSwap | One global curvature scalar $A$, same shape for every asset | Single scalar $A$ | Unbounded |
| Gyroscope E-CLP | Fixed parametric family (ellipse: $a$, $b$, rotation) | Bounded but locked to one family; no multimodal, fat-tail or asymmetric density | Bounded to the ellipse |
| Uniswap v3/v4 | Stack of flat ticks approximating a shape | Many LP positions; floors at 1 tick; no sub-tick | Per-position, flat within tick |
| **BTR AIMM** | **Chosen monotone C2 density, integrated to offset(depth)** | **One shape, per-asset width, hot-swappable under timelock; $H$ continuous below 0.5 bp** | **Hard-cut at $\pm H$ by construction** |

### 12.3. Bounded tails

$H$ is an explicit two-sided bound: 100% of the leg's liquidity sits within $\pm H\cdot\kappa/\text{dispRef}$
of the mark, by construction of the q70 truncation. Constant-function market makers cannot do this:
their invariants have unbounded support and always quote some depth across the entire price axis,
spending capital on offsets that never fill. AIMM cuts both tails.

The corollary is that AIMM's book **exhausts**. Past $\pm H$ there is nothing: $v_f$ clamps at BPS,
$x$ clamps at the domain edge, and the quote stops improving for the taker.

### 12.4. One machine, wide or narrow

The same machinery spans the full concentration range without changing the engine or the shape. An
operator picks wide or narrow per asset by pointing at a $(H, \text{dispRef})$ codebook entry and
letting `minDispersionPbps` scale it continuously. The live ladder runs from 1.61 bp (USDT) to 37.40 bp
(KRW1), a 23x range, across three stored shape vectors.

---

## 13. Width selection

The shape family is fixed. The only per-asset choice is width: the $H/\text{dispRef}$ ratio of the
codebook entry the asset points at, plus the asset's own `minDispersionPbps`.

| Asset class | $H$ (bp) | `dispRefPbps` (pbps) | Preset |
|-------------|----------|-------------------|--------|
| Tight-peg stables, coverage-walled | 1 | 100 | 2 |
| Wider stables and newer pegs | 2 | 100 | 3 |
| Widest stables | 5 | 100 | 4 |
| Volatiles, gold, FX | 5 | 500 | 5 |
| Base numeraire (no wall) | 1 | 100 | 1 |

> **Derive width from observed density, not from a class label.** The preset assignment, the
> dispersion band and the fee floors are read off the asset's measured return density over a
> multi-week window of NXR history, never from a class label or a volume ranking. The mapping is
> mechanical: upper-tail move size selects the codebook entry and drives the `maxDeviation` floor,
> realized and depeg volatility grade the fee floor, and an asset whose tail is too heavy to admit a
> finite-variance fit is barred from the tightest entry however tight its central core looks. The
> table above is a summary of where the shipped legs landed, not the rule that put them there. The
> shipped generator records its own rule in the published deployment parameters:
> $S_{\text{dep}} = \max(q70(|\ell|),\ 2\theta,\ \text{feeQ99})$ from the
> two-week fee-kernel density, which is why the roster's majors land near feeQ99 (19 to 21 bp, so
> `minDispersionPbps` 1900 to 2100 at preset 5) rather than at the q70 shape cut near 11 bp. A
> pool-creation wizard surfaces the measured density and its implied width; it does not ask the
> operator to guess a shape, because the shape is not a choice. The estimators, windows and quantile
> cutoffs are internal calibration; the outputs are not secret, since every parameter lands in on-chain
> state and in the published deployment parameters. Assets with no clean tape hold their
> prior values until the history exists.

---

## 14. Related documentation

- [Inventory Management](/docs/1-1-1-inventory-management): coverage, skew and depth, the three inputs
- [Anchor Path Pricing](/docs/1-1-3-anchor-path-pricing): marks, leg orientation and multi-hop composition
- [Spread & Fees](/docs/1-1-4-spread-fees): the fee charged on top of the traversal
- [Slippage & Price Impact](/docs/1-1-5-slippage-price-impact): the full decomposition and taker guidance
- [Parametrization](/docs/1-1-7-parametrization): tuning guide
- [Invariants](/docs/1-1-8-invariants): the properties the curve must preserve
