# The PaceWise race projection model

> Markdown copy of https://pacewise.doughworks.dev/model, generated from the page. The page is canonical. Every page on this site has one; the index is https://pacewise.doughworks.dev/llms.txt.

Every algorithm PaceWise uses to turn a runner's results into projected race times, age grades, splits and pacing plans, in mathematical terms, with the constants and the sources.

Edition 2, September 2026: the marathon knee (3.3). First published here 16 September 2026. Open for review.

> Most race predictors take one result and one textbook curve. The **PaceWise model** (section 3) blends *every* result a runner has logged, each with its distance, time and date, into a personal projection band at the target distance: it fits the runner's own fatigue exponent, weights each result by how recent it is and how close its distance sits to the target, and widens the band when the evidence disagrees or has to stretch far. It is closed-form, deterministic, runs offline, and reduces to Riegel when the data can only support that.
>
> The classical single-result models (section 2), the pacing curve (section 4) and WMA age grading (section 5) are specified alongside it so the whole calculation surface can be checked as one. This page is written for a reader with a sports-science or applied-maths background who wants to check the formulas against the literature. Corrections are welcome: [support@doughworks.dev](mailto:support@doughworks.dev).

**Notation.** Times are in seconds (`t`), distances in metres (`d`), pace in seconds per metre. `ln` is the natural logarithm. All models are pure, deterministic and offline. Non-positive or non-finite inputs raise a typed error rather than returning `NaN` or infinity (section 6).

## 1. Foundations: the pace identity

All elementary conversions are the single identity

```
t = p · d          p = t / d          d = t / p          v = 1 / p
```

with `t` seconds, `d` metres, `p` seconds per metre and `v` metres per second. Display units (min/km, min/mile) are applied at the surface; the core is unit-free canonical SI. There is nothing to validate here beyond dimensional consistency and the guard rails of section 6. It is listed because every higher model reduces to it.

**Session aggregate.** For segments `(t_i, d_i)` the total is `T = Σt_i`, `D = Σd_i`, average pace `T/D`. Each segment must be individually positive: a negative leg is rejected, never allowed to cancel another into a plausible total.

## 2. Classical race-equivalence models

Given **one** known performance `(t₁, d₁)`, predict the time `t₂` at a target distance `d₂`. PaceWise offers three textbook models so a runner can cross-check; they are also the fallback whenever only a single result exists. A fourth option (2.4) applies the same single-result form with the runner's *own* fitted exponent. It belongs in this section rather than section 3 because it too is a pure function of one performance. The [race time predictor](https://pacewise.doughworks.dev/race-time-predictor) runs the three textbook models side by side.

### 2.1 Riegel power law

Peter Riegel's endurance model (Riegel, *American Scientist*, 1981):

```
t₂ = t₁ · (d₂ / d₁)^k
```

`k` is the **fatigue exponent**. `k = 1` would mean pace is independent of distance; real endurance running has `k > 1` because sustainable pace fades as distance grows. The literature value, and the default here, is `k = 1.06`: the canonical exponent for roughly 1.5 km to 42.2 km and the constant behind most public race predictors.

> **Cross-check.** A 20:00 5K (1200 s) to 10K gives `1200 · 2^1.06 = 2502 s = 41:42`, matching Omnicalculator's race predictor and The Running Genie to the second.

### 2.2 Cameron model

David Cameron's fitted model is steadier than Riegel over long extrapolations. With distances in **miles** (`d̃ = d / 1609.344`):

```
t₂ = (t₁ / d̃₁) · d̃₂ · a(d̃₁) / a(d̃₂)
a(x) = 13.49681 − 0.048865·x + 2.438936 / x^0.7905
```

`a(x)` is an empirical per-mile cost curve. It **crosses zero near 277 miles** (about 446 km); beyond that the fit is meaningless, so the model rejects the target rather than return a negative or spuriously positive time.

> **Cross-check.** 20:00 5K to 10K under Cameron is 2499.66 s (about 41:40), within half a second of the closed-form value, and within 5% of Riegel on a one-octave extrapolation.

### 2.3 Daniels & Gilbert VDOT

A VO₂-based physiological model (Daniels & Gilbert, 1979). For a race of `d` metres in `t` seconds, let `m = t/60` minutes and velocity `v = d/m` in metres per minute:

```
VO₂(v)        = −4.60 + 0.182258·v + 0.000104·v²        (oxygen cost of pace)
%sustainable  = 0.8 + 0.1894393·e^(−0.012778·m)
                    + 0.2989558·e^(−0.1932605·m)        (fraction of VO₂max held)
VDOT(d, t)    = VO₂(v) / %sustainable
```

The runner's VDOT is fixed by the known race. The equivalent time at the target is the `t₂` that reproduces that same VDOT. `VDOT(d₂, ·)` is **monotonically decreasing in time over the positive-VO₂ region**. `VO₂(v)` goes negative below about 25 m/min (a 6.7-hour 10K), so strict global monotonicity fails only at implausibly slow paces well outside any real target. Within the region every real target occupies the root is unique, so it is found by bisection over `[1 s, 86 400 s]`: 100 halvings, far below the 1 µs tolerance. A target whose equivalent falls outside the 24-hour bracket is rejected rather than silently saturated. The [VDOT calculator](https://pacewise.doughworks.dev/vdot-calculator) shows both equations with a worked example.

> **Cross-checks.** From a 22:00 5K (VDOT about 45, per Jack Daniels' tables): 10K about 45:40 (2740 s, ±15 s), half marathon about 1:40:30 (6030 s, ±60 s), marathon about 3:30:00 (12 600 s, ±30 s).

### 2.4 PaceWise per-result equivalence

The equivalent-times surface offers the PaceWise model in the same single-result form as the three textbook models: the power law of 3.3, marathon knee included

```
t₂ = t₁ · (d₂ / d₁)^k · exp(κ·s⁺),     s⁺ = max(0, ln(d₂ / max(d₁, h)))
```

with `k` the runner's **fitted, trust-shrunk** fatigue exponent of 3.1, fitted once from the full per-distance frontier and then applied to the one result being converted, and `κ`, `h` the knee of 3.3. The knee applies here too, so a result's marathon equivalent and the projection from that same result never disagree. A runner with fewer than two distinct distances gets the Riegel default `k = 1.06`, so for a target at or below the half the option degrades to 2.1 exactly; past the half it reads `κ` steeper than Riegel. Unlike the three formulas, the conversion carries a band: the stance `α ∈ [−1, +1]` sweeps

```
t₂(α) = t₂ · exp(−α · σ),     σ = min( σ_min + β · |ln(d₂/d₁)|,  σ_max )
```

which is the 3.4 width with the variance term identically zero. A single result cannot disagree with itself, so only the extrapolation floor (`σ_min = 0.01`, `β = 0.015`, capped at `σ_max = 0.10`) contributes, and the band is log-symmetric exactly as in 3.4.

**The proven-best floor of 3.5 is deliberately absent here.** Equivalence describes *one result*: what that run is worth at another distance, a pure function of it. The floor's job is to keep a *projection of the runner* from contradicting proof. Flooring the equivalence would re-couple every result's table to the whole stored history, exactly the blending this per-result form exists to avoid. When the runner's stored best at the target already beats the conversion, the apps draw the comparison beside the number (a "Your PB" badge) instead of folding it in.

The supplied exponent is rejected if non-finite, never silently defaulted, then defensively clamped to the physiological `[1.02, 1.15]` range of 3.1.

> **Validation.** From the device-reported fixture of 3.1 (5K 1331 s, 10K 2733 s, half 6594 s, aged 61, 61 and 75 days) the fit gives `k ≈ 1.0677`, and the half converts to a 10K of 2971.6 s, about 49:32 (stance ±1 gives 48:29 and 50:35). Deliberately *not* floored at the stored 45:33 10K. The test suite also pins that `k = 1.06` reproduces 2.1 verbatim below the knee, that a half's marathon equivalent equals the 3.3 projection from it, that the band is log-symmetric (`t(−1)·t(+1) = t(0)²`), the single-result fallback, the exponent clamp, the band ceiling under extreme stretch, and the section 6 rejection cases.

**Reviewer note.** The three textbook models are intentionally redundant; the suite asserts they sit within 5% of one another on a one-octave extrapolation. Divergence grows with extrapolation distance, which is exactly the motivation for the PaceWise model.

## 3. The PaceWise model

**The contribution.** Where section 2 extrapolates from a *single* performance, PaceWise blends **every** result a runner has logged, each with its distance, time and optional date, into a personalised projection **band** at the target, swept by an aggressiveness stance. It is fully closed-form (no iteration), deterministic, and reduces gracefully to Riegel when the data can only support that.

Inputs: a set of results `{(t_i, d_i, a_i)}`, where `a_i` is the result's **age in days** before the reference date (`a_i < 0` means undated); a target distance `D`; and an aggressiveness stance `α ∈ [−1, +1]`. Output: a projected time at `D`.

The model has three stages, plus a proven-best floor on the output (3.5).

### 3.1 Stage 1: the personal fatigue exponent

Rather than assume Riegel's `k = 1.06`, PaceWise **fits the runner's own** `k` from the slope of `ln t` against `ln d`. Taking logs of the power law `t = c·d^k` gives the linear relation

```
ln t = ln c + k · ln d,
```

so `k` is the slope of a least-squares line through the runner's results in log-log space. Two refinements:

1. **Per-distance frontier.** Only each distinct distance's *fastest* result is fitted (group by rounded metres, keep the minimum time), so slow repeats of one distance do not masquerade as fatigue.
2. **Recency weighting.** Each frontier point carries the **fit weight** `w_i^fit = r(a_i)`, the recency decay of 3.2, so the slope tracks current fitness. Stages 2 and 3 use a *different* weight `w_i^blend`, recency × proximity; the two are kept distinct deliberately.

The weighted least-squares slope is

```
        Σ wᵢ^fit (xᵢ − x̄)(yᵢ − ȳ)
k =  ──────────────────────────────  ,   xᵢ = ln dᵢ,  yᵢ = ln tᵢ,
            Σ wᵢ^fit (xᵢ − x̄)²
```

with weighted means `x̄ = Σwᵢ^fit·xᵢ / Σwᵢ^fit` and `ȳ = Σwᵢ^fit·yᵢ / Σwᵢ^fit`.

**Fallbacks and clamps.**

- Fewer than two distinct distances: no slope, so `k = 1.06`.
- Weighted **variance** `Σwᵢ^fit·(xᵢ−x̄)² / Σwᵢ^fit < 10⁻⁴` (distinct distances within about 2% of each other, numerically unstable): `k = 1.06`. Dividing by `Σwᵢ^fit` makes the threshold scale-invariant. It no longer shifts with the absolute weight magnitude, so two runners with the same distance spread but differently aged results get the same fit-or-fallback decision.
- Otherwise `k` is clamped to the **physiological range** `[1.02, 1.15]`. `k = 1.0` would be no fatigue at all; above 1.15 is steeper than real endurance running. The clamp is on the fitted `k` alone: past the half marathon the law fades at `k + κ` (3.3), which may exceed 1.15 by design.

**Fit-quality shrinkage.** A slope that survives the guards above is still only trusted as far as the frontier actually agrees on it. Let `k_ls` be the raw least-squares slope (before the clamp) and define the fit's **weighted residual variance** in log-time,

```
v = Σ wᵢ^fit · ( (yᵢ − ȳ) − k_ls·(xᵢ − x̄) )² / Σ wᵢ^fit ,
```

the scatter of the frontier about the line the fit actually drew. The clamped slope is then shrunk toward the Riegel default in proportion to that scatter:

```
trust = τ / (τ + v),        τ = 10⁻⁴,
k     = 1.06 + trust · ( clamp(k_ls, 1.02, 1.15) − 1.06 ).
```

`τ` is the residual variance at which the personal slope earns exactly half trust; in log-time, `v = 10⁻⁴` corresponds to residual scatter of about 1% in time. No re-clamp is needed: the result interpolates two values already inside `[1.02, 1.15]`. Three properties matter for review:

- **Clean fits are untouched.** `v = 0` gives trust 1, so a frontier one power law explains exactly returns the unshrunk slope bit-identically. This includes **every frontier of exactly two distinct distances** (a line through two points has zero residuals), so the model's two-result behaviour is unchanged.
- **Weight-scale invariance.** Like the minimum-variance guard above, `v` is a *variance* (divided by `Σwᵢ^fit`), not a raw weighted sum, so the trust decision does not shift when a runner's results all age together.
- **Residuals are measured against the raw slope**, not the clamped one. The question is how well *one* power law explains the frontier, and the raw least-squares line is the best such law; measuring scatter about the clamped slope would conflate fit quality with clamp distance.

The motivation: `wᵢ^fit` is recency-only, so unlike the blend, whose proximity factor (3.3) halves a far result's vote per distance-doubling, a lone far-distance result enters the slope fit at nearly full weight, and because the exponent is shared by every candidate it can drag the *entire* blend, unopposed. In the device-reported case that motivated the mechanism, one weak half marathon beside a consistent 5K and 10K steepened `k` to 1.112 and pushed the raw 10K blend 61 s slower than the runner's proven 10K. Under shrinkage that frontier's scatter (`v ≈ 5.8·10⁻⁴`) earns the fitted slope only 0.15 trust, `k` settles at 1.068, and the projection recovers a usable band (the 3.5 floor still pins the likely time at the proven best).

The fitted exponent is also exposed on its own, the identical value the blend fits internally, so the per-result equivalence of 2.4 converts through the same personal profile the projection uses.

> **Validation.** Three results placed exactly on a `k = 1.10` law project a fourth distance back onto that law to a relative error below 10⁻⁹ (an exact law has `v ≈ 0`, so shrinkage leaves it alone). Separate cases exercise the implausible exponent clamp, the repeats-of-one-distance and near-identical-distance fallbacks, and the scale-invariant variance threshold. The shrinkage is pinned by the device-reported shape above (a lone weak outlier no longer collapses the same-distance band), by two-distance frontiers keeping full trust, by a lone *strong* outlier still speeding the projection (the correction is symmetric, not slow-biased), and by the trust decision surviving a common ageing of the results.

### 3.2 Recency weighting

A result's vote decays exponentially with age, half-life `H = 180 days`:

```
r(a) = exp(−ln2 · a / H),     a = aᵢ if aᵢ ≥ 0 and finite, else H.
```

So a six-month-old result counts half as much as today's. An undated result is pinned at exactly one half-life: kept as evidence at half a vote, but unable to outvote a fresh dated result. The `aᵢ ≥ 0` guard folds a *future-dated* result (negative age relative to the reference date) into the same undated bucket; the two are indistinguishable to the model, which is acceptable since a future race date is not a meaningful input here.

**Why 180 and not 365.** Fitness moves on a months-to-seasons timescale, but a one-year half-life let evidence a runner had visibly outgrown keep a third to a half of the blend. A 17-month-old half marathon still held 36% of the vote at its own distance, and a runner mid-breakthrough had months of gains averaged against the form they started with. Halving `H` corrects those without touching a runner whose results agree with each other, and it *strengthens* the mirror case: where a fresh same-distance result genuinely is the best evidence, its share rises from 69% to 78%. Reweighting by *distance* instead was measured and rejected.

**Why not shorter.** Below roughly 120 days the model begins projecting long races from short-distance speed alone while simultaneously *narrowing* the band, because a dominant single candidate has nothing left to disagree with it. A sharp 5K and 10K pair six months on from a real 3:45 marathon projects **2:58** at a 60-day half-life: 47 minutes faster than the runner has ever raced, in a band 20 minutes tighter than the current one. That is the confident-nonsense failure the `σ_min + β·s̄` floor (3.4) exists to prevent, and the half-life must not undo it. 180 days sits with room on both sides.

### 3.3 Stage 2: evidence blending (the likely time)

Every result is projected to the target `D` through the personal power law, **in log-time space**, where race times compose multiplicatively, so the natural average is geometric and the band is symmetric. Define each candidate's log-time and its weight:

```
sᵢ = ln(D / dᵢ)                        (the "stretch": log-distance to target)
sᵢ⁺ = max(0, ln(D / max(dᵢ, h)))       (the part of the stretch past the half)
ŷᵢ = ln tᵢ + k · sᵢ + κ · sᵢ⁺          (result i's projection of D, in log-time)
wᵢ^blend = r(aᵢ) · exp(−|sᵢ|)          (recency × distance-proximity)
```

**The marathon knee.** `h = 21 097.5 m` (the half marathon) and `κ = 0.07`: the law is the runner's `k` up to the half and `k + κ` beyond it, continuous at `h`. Only the stretch past the half pays, and only going up. A 30K result projected to the marathon pays `κ` from 30K; a marathon projected down to 10K pays none.

*Why.* Without it the likely marathon was more optimistic on real outcomes than any other target. Against 70,513 thepowerof10 cases (3.6), the median of `(actual − likely)/likely` was +2.2 to +4.1% for runners with a marathon in their evidence and +7.6 to +8.8% for a first marathon, where 5K to half targets read +1.0 to +3.1%. Neither the recency half-life (90 to 365 days), the default exponent, nor a weaker stale same-distance result moved it; extra fade past the half did.

*Shape.* Cameron's model (2.2), whose local exponent rises from 1.059 (10K to half) to 1.0875 (half to marathon), is the precedent for a steepening at the half. A knee at 10K, smooth curvature, or a bend below the half reached the same marathon medians but made the 5K to half targets worse, so the knee is upward-only and sits at the half. Daniels VDOT (2.3) flattens past the half instead, so this is not something every classical model shares.

*Size.* Every target on that data carries the same selection bias: the evidence is each runner's fastest result per distance, the outcome a single later race. So `κ` removes the marathon's *excess* over the other targets rather than zeroing its median. The excess-zeroing value is 0.061 to 0.091 across the three protocols (bootstrap 95% intervals 0.044 to 0.099). On a second population, Vickers and Vertosick's 2016 survey of 2,303 US recreational runners (CC BY 4.0, never used to fit `κ`), it is 0.059 to 0.078, and `κ = 0.07` takes the marathon predicted from shorter races from **+5.5% to +0.5%** at the median.

With the knee, the marathon's median error matches the other targets' (+2.0 against +1.9%, +1.3 against +1.0%, +3.0 against +3.1% by protocol), and a first marathon reads +2.5 to +3.6%. What remains on every target is what "likely" means off PB evidence, not a distance effect.

This blend weight is distinct from the fit weight `wᵢ^fit` of 3.1: the fit tracks fitness over time (recency only), while the blend additionally trusts *nearby* races more. The proximity factor `exp(−|sᵢ|)` means a result **one distance-doubling away carries half the vote** of one at the target distance. Near evidence dominates far evidence. The **likely** log-time is the weighted mean

```
μ = Σ wᵢ^blend·ŷᵢ / Σ wᵢ^blend,
```

and the likely time is `exp(μ)`: a recency- and proximity-weighted **geometric mean** of the individual projections.

> **Sanity.** A single result at the target returns itself exactly; a single result off target reduces to plain Riegel at 1.06 up to the half, and to Riegel plus the knee past it: a 20:00 5K projects a 3:21:21 marathon, against Riegel's 3:11:49. Recency dominance, the reduced vote of undated results, proximity dominance, and the knee's continuity and monotonicity are each pinned by a test.

### 3.4 Stage 3: the stance band

The weighted spread of the candidates measures how much the runner's own evidence **disagrees** about `D`:

```
σ² = Σ wᵢ^blend·(ŷᵢ − μ)² / Σ wᵢ^blend     (weighted variance of the log-projections)
```

This is floored by a term that grows with how far the evidence had to extrapolate, and capped so a wildly disagreeing set still yields a usable band:

```
s̄ = Σ wᵢ^blend·|sᵢ| / Σ wᵢ^blend          (mean stretch)
σ = min( max( √σ²,  σ_min + β · s̄ ),  σ_max )    (floor first, then ceiling)
σ_min = 0.01   (±1 % floor at zero extrapolation)
β     = 0.015  (extra half-width per unit of |ln(D/d)|)
σ_max = 0.10   (±10 % ceiling)
```

The floor-then-ceiling order is load-bearing. Past `s̄ = (σ_max − σ_min)/β = 6`, reachable only by extreme extrapolation (a 100 m result projected to the marathon has `s̄ = ln(421.95) ≈ 6.05`), the floor `σ_min + β·s̄` exceeds the ceiling `σ_max`, and the outer `min` pins σ to `σ_max`. A three-argument `clamp(value, lo, hi)` with `lo > hi` is **undefined** (it would throw), so an implementation must apply the ceiling last, not nest the two bounds.

The stance `α` sweeps the band **log-symmetrically**:

```
t(α) = exp( μ − α · σ ),     α ∈ [−1, +1]  (clamped; non-finite ⇒ 0).
```

`α = 0` is the likely time, `α = −1` conservative (slower), `α = +1` aggressive (faster). Because the sweep is linear in log-time, the band is symmetric in the multiplicative sense:

```
t(−1) · t(+1) = t(0)²              (conservative × aggressive = likely²)
```

**What the band is.** σ is the scale of the runner's own evidence: how much it disagrees, floored by how far it had to stretch. It is a **stance sweep, not a prediction interval**. Measured against 70,513 real cases (3.6), the −1 to +1 sweep contains the runner's next race 13 to 18% of the time at a median half-width of 0.7%. Read `t(±1)` as what a conservative and an aggressive target look like at that scale, with `t(0)` as the point estimate.

> **Validation.** The identity above is pinned to relative 10⁻⁶. Further cases pin that conservative is slower and aggressive faster, that the stance clamps beyond the band, that a non-finite stance reads as likely, that the band widens with extrapolation (5K to marathon wider than 5K to 10K), and the floor-versus-ceiling inversion at extreme extrapolation (100 m to marathon). The log-symmetry identity holds while the proven-best floor (3.5) is slack; when the floor binds it wins.

### 3.5 The proven-best floor

The blend can produce a projection at a distance the runner has already raced that is *slower than their own supplied best at that distance*. A fresh 10K PB beside a comparatively weak half marathon drags the blended 10K projection below the proof. The fit-quality shrinkage of 3.1 bounds the *exponent* channel of that drag; the direct blend pull of the weak result remains, so the floored case still arises. Mathematically defensible (the other evidence genuinely argues current endurance is worse), but as a product number it undermines trust: "you'll likely run 49:49" under a just-logged 49:16 reads as a bug, not a nuance. So the model floors its output at the fastest supplied result at the target distance:

```
t_final(α) = min( t(α),  min{ tᵢ : |ln(D / dᵢ)| ≤ 10⁻⁴ } )
```

Properties:

- **Applies at every stance.** Even the conservative edge may not read slower than proof. When the floor binds it deliberately breaks the 3.4 log-symmetry identity (`t(−1)·t(+1) = t(0)²` holds only while the floor is slack) and can collapse part of the band onto the proven time. Proof beats symmetry.
- **Never speeds up a slack projection.** A proven best *slower* than the blend leaves the blend untouched (`min` only ever lowers), so a stale or weak same-distance result cannot drag a faster projection down to it.
- **Distance matching is tolerant.** "Same distance" means within `10⁻⁴` in absolute log-distance (about 0.01%), so metre-rounding conventions across surfaces and platforms (a half marathon as 21 097, 21 097.5 or 21 098 m) cannot defeat the floor. This is far below the roughly 2% gap 3.1 treats as *distinct* distances.
- **It binds for most marathoners.** With the 3.3 knee, a runner's shorter results project a slower marathon than before, so for 61 to 64% of thepowerof10 runners with a marathon in their evidence (19 to 22% before the knee) the likely marathon *is* their proven best, with the conservative edge pinned there too. That is the floor working as intended: those runners' next marathon still landed a median 1.4 to 3.3% slower than it.
- **Staleness stays the caller's concern.** The floor considers exactly the results passed in. The apps' recency pooling (which sets aside PBs older than 18 months while fresher ones exist) has already vetted them, so an ancient proof only reaches the floor when nothing fresher exists at all.

Downstream the floor is a *projection* concept: the goal-readiness verdict (Appendix B) grades against the floored likely time, and the race surfaces publish the floored blend. The equivalent-times table does **not** consume the blend. It converts the single result on screen (2.4) and is deliberately unfloored there, the cross-result comparison being the UI's badge to draw. The pacing-plan curve (section 4) is untouched: it shapes a *chosen* goal time and never consumes the projection.

> **Validation.** Tests pin the headline case (blend slower than a fresh 10K PB returns the PB), the slack direction (a slower same-distance best leaves the blend untouched), no off-distance leakage, the conservative edge of the band, and the 21 097.5 versus 21 098 m match.

### 3.6 Design rationale

| Choice | Why |
| --- | --- |
| Log-time space throughout | Race times scale multiplicatively; a geometric mean and a log-symmetric band are the natural, scale-invariant operations. |
| Personal `k`, clamped and trust-shrunk | Honours the runner's own fatigue signature when the data supports it, never leaves the physiological envelope or trusts an ill-conditioned fit, and hands a scattered fit back toward Riegel rather than letting one off-line result steepen every candidate. |
| Recency half-life 180 days | Fitness drifts on a months-to-seasons timescale. Half a season keeps stale PBs as weak priors without letting them dominate, and is short enough that evidence a runner has outgrown actually fades (3.2). |
| Proximity `exp(−\|s\|)` | A 5K is weak evidence for a marathon. Weighting by log-distance encodes "trust nearby races more" without a hard cutoff. |
| Marathon knee `κ = 0.07` past the half | Measured: projections past the half were the only ones more optimistic than the rest, on a club and a recreational population alike. Upward-only, because bending below the half made the shorter targets worse (3.3). |
| Variance-driven band, floored by extrapolation | The band should widen both when the runner's results *disagree* and when the model *extrapolates far*, carrying both in one width. It is a scale, not a coverage target (3.4). |

**Known limitations and open questions for review.**

- The band is a **stance sweep, not a calibrated statistical prediction interval**, and this is now measured rather than asserted. Against 70,513 cases built from thepowerof10 (Blythe and Király, CC BY 4.0), coverage of the −1 to +1 band is **17.8%** (fastest race in the 90 days after a held-out cut), **17.8%** (365 days) and **12.6%** (the next race exactly as run), at a median half-width of **0.7%** (18.9, 19.4 and 13.9% at 0.8% before the 3.3 knee, which sends more marathon cases to the floor). 80% coverage on that population would need roughly **±7%**, and 55 to 68% of misses are on the slow side. `σ_min`, `β` and `σ_max` remain the scale of the evidence's disagreement, not a fitted coverage target, and the proven-best floor (3.5) pins the conservative edge at the runner's own best by design, so 45% of cases cover at half the rate of the rest whatever σ is.
- The *likely* time carries a small **optimistic bias** on the same data, separate from the band: the median of `(actual − likely)/likely` is **+1.1% to +3.1%** by protocol, with a long slow tail (p90 +9.2 to +13.5%). The marathon-specific part (+2.9 to +4.6%, and +7.6 to +8.8% for a first marathon) is what the 3.3 knee removes. What is left is the same on every target: whether "likely" should promise a best-to-best outcome (about +1%) or the next race as run (about +3%) is open, and deliberately not absorbed into `κ`.
- The knee is fitted on two populations, one of them undated and self-reported, and is untested past the marathon. Its extra fade grows with log-distance, so a 100K projection from a 3:30 marathon reads about 6% slower than the plain power law: sensible in direction, unmeasured in size.
- Being upward-only, the law does not invert past the half: a half converts up to a marathon at `k + κ` over the last doubling, but that marathon converts back down at `k`, to a half about 5% slower than the one it came from. A two-way bend would restore the symmetry and was measured: it made thepowerof10's 5K to half targets worse, while the recreational set mildly favoured it. Mixed evidence, so the simpler form stands. Relatedly, 3.1 fits `k` to raw frontier times, so a runner whose frontier includes a marathon has some of the knee already in `k`; fitting on knee-corrected times moved the medians by ±0.2% at most.
- The personal-`k` fit uses *fastest-per-distance* points, discarding within-distance scatter. This trades statistical efficiency for robustness to junk data. Worth a second opinion.
- Recency and proximity weights are multiplied independently. There is no interaction term: a recent *and* near result is not super-weighted beyond the product.
- An *asymmetric* disagreement in the evidence (one slow outlier) shifts the blend mean rather than widening the band. The fit-quality shrinkage of 3.1 bounds the exponent channel of that shift, but making disagreement widen σ itself remains an open tuning question.

## 4. Pacing-plan curve

A pacing plan distributes a goal time `T` across the race with a signed **bias** `s` (negative split versus positive split). With `f` the fraction of *distance* covered, the fraction of *goal time* elapsed is

```
E(f, s) = f · (1 + s · (1 − f)),     s ∈ [−0.06, +0.06].
```

Properties the maths guarantees, for any `|s| < 1`:

- `E(0,s) = 0` and `E(1,s) = 1`: the plan **always lands exactly on the goal time**, regardless of bias.
- `E` is monotonic in `f`: cumulative time never goes backwards.

`s` is clamped to ±0.06 (±6% peak deviation from even pace) so a generated plan stays physically sensible. Cumulative split times are `T · E(d_m/D, s)` at each marker `d_m`; the final row lands on `T` by construction. The [pace band generator](https://pacewise.doughworks.dev/pace-band) prints this curve.

## 5. WMA age grading

Age grading normalises a performance against the world-best standard for the athlete's age and sex, using the **World Masters Athletics (WMA) 2020 factor tables** (compiled by Alan Jones; encoded by the Runalyze age-grade project, CC BY 4.0). For a time `t` over distance `d` by an athlete of given age and sex, with age factor `φ ≤ 1` and open-class standard `S` (both read from the tables, linearly interpolated across the 28 standard distances):

```
age-graded time  = t · φ            (the open-class-equivalent "aged-up" time)
age-grade %      = (S / φ) / t · 100.
```

`S/φ` is the *age standard*: the world-best time expected at that age. Ages outside the table's 5 to 100 range are clamped; distances outside the table's columns use the nearest column outright (no extrapolation). The factor tables are machine-generated for every platform from one upstream fetch, so the apps and the [age grade calculator](https://pacewise.doughworks.dev/age-grade-calculator) cannot drift.

This section is **strictly descriptive** (it reports against a published standard). The only thing to validate is faithful table reproduction and correct interpolation, both unit-tested against published values.

## 6. Numerical-safety contract

Every entry point enforces the same discipline, which a reviewer can rely on:

- **Domain guards.** Inputs that are zero, negative, non-finite or empty raise a typed error (non-positive time, non-positive distance, empty results, and so on), never a silent `NaN` or infinity.
- **Result guards.** Even *finite* inputs whose computation overflows (a Riegel ratio whose power exceeds double range, or a PaceWise projection that overflows) are rejected by a finiteness check on the output, rather than returned as infinity.
- **No saturation masquerading as an answer.** Bracketed solvers (VDOT) and fitted curves (Cameron) reject targets outside their valid domain instead of returning a clamped endpoint.
- **Determinism.** No randomness, no clock reads inside the maths (the reference date and result ages are passed in), no platform-dependent floating-point paths. One shared core is the single source of truth; the app, and the calculators on this site, are thin layers over it.

## 7. How it is checked

The numbers quoted in the cross-check and validation boxes above are not illustrations. Each is a case in the core's test suite, which runs on every change on both the JVM and native targets. The apps call the same core, and the [web calculators](https://pacewise.doughworks.dev/calculators) on this site run a line-by-line port of it that a build check re-tests against the core's own cases before anything deploys. A formula on this page, the number in the app, and the number in the browser cannot disagree without that check failing.

If you find a formula, constant or citation here that is wrong, or a case the model handles badly, email [support@doughworks.dev](mailto:support@doughworks.dev). The core and its tests are the authoritative reference; this page follows them, and a correction that lands there will be reflected here with a new edition line.

## A. Constants at a glance

| Symbol | Value | Meaning |
| --- | --- | --- |
| `k` (default) | 1.06 | Riegel fatigue exponent, and the PaceWise fallback |
| `k` range | [1.02, 1.15] | physiological clamp on the fitted exponent (past the half the law is `k + κ`) |
| `h` | 21 097.5 m | the marathon knee: where the law steepens (3.3) |
| `κ` | 0.07 | extra fatigue exponent past `h`, upward only (3.3) |
| `H` | 180 days | recency half-life (and the assumed age of undated results) |
| `σ_min` | 0.01 | band half-width floor (about ±1%)† |
| `β` | 0.015 | extra band half-width per unit of log-distance extrapolated |
| `σ_max` | 0.10 | band half-width ceiling (about ±10%)† |
| min weighted log-variance | 10⁻⁴ | below this (distances within about 2%) the exponent fit is abandoned |
| `τ` (exponent fit trust) | 10⁻⁴ | weighted residual variance of `ln t` at which the fitted exponent earns half trust (shrunk toward 1.06) |
| same-distance tolerance | 10⁻⁴ | `\|ln(D/d)\|` within this counts as *the* distance for the proven-best floor |
| pacing bias `s` | [−0.06, +0.06] | ±6% peak deviation from even pace |
| VDOT bracket | [1 s, 86 400 s], 100 steps | bisection domain and precision |
| Cameron domain | `a(x) > 0` (below about 277 mi) | fitted-curve validity |

† The ±x% gloss is a first-order reading of the *log-space* half-width σ. The band is exactly symmetric multiplicatively, not linearly: σ = 0.10 spans `exp(±0.10)`, which is **+10.5% / −9.5%**, not a literal ±10%. The percentages are a convenient linear approximation for small σ. Neither bound is a coverage claim: what the sweep actually contains is measured in 3.6.

## B. The goal-readiness verdict

A small helper grades a goal against the model's *likely* projection. Both are first rounded to whole seconds, the precision every surface displays, so the verdict cannot contradict the shown numbers:

```
likely ≤ goal              → achievable
goal < likely ≤ goal·1.05  → ambitious   (within 5 %)
likely > goal·1.05         → stretch
```

## Citing this page

doughworks (2026). *The PaceWise race projection model*, edition 2. [https://pacewise.doughworks.dev/model](https://pacewise.doughworks.dev/model). The formulas and constants are free to reproduce with a link back; the WMA factor tables carry their own CC BY 4.0 attribution to Alan Jones and the Runalyze project.
