Odds Generation — game · set + set · set (tennis)
Enter the match's Game Handicap and Game Total in Malay and its Moneyline in decimal — margins still embedded. Tennis scores are not counts: they are a nested race — point → game (win by 2 from deuce) → set (first to 6 by 2, tiebreak at 6-6) → match — so totals come out bimodal (straight sets vs a decider) with a parity comb (a set never totals 11 games; a 7-6 counts 13). The engine (ADR-030):
- strips each bookmaker margin (Power method — every book here is two-way; tennis has no draw);
- pins the serve sum pA + pB to the tour/surface prior (ATP ≈ 1.29, WTA ≈ 1.14) and bisects the serve difference against the Game Handicap — the match quotes leave the sum on a near-flat ridge, so alone they cannot fit it;
- breaks the ridge with the Set-1 Total when the book quotes set 1 (Saba does): more serve ⇒ more holds ⇒ longer sets, so P(set-1 over) rises monotonically with the sum — stage Set 1 Σ bisects the sum against that quote and the prior becomes a fallback (a shape prior upgraded to a per-match reading, ADR-030 O2);
- consumes the Game Total with whichever knob can reach it: τ, a match-level form mixture (matches shorten), or extra serve sum (matches lengthen; disabled once set-1 pinned the sum — τ only) — the difference re-solves at every trial;
- never fits the Moneyline or the Set-1 Handicap: they are the cross-anchor consistency readouts (Δ ML, Δ S1hcp) — a large value means the book's quotes disagree with each other, a risk signal;
- prices both scopes off the one solved distribution — set · set (Moneyline, Set Handicap, Set Total) and game · set (match game lines plus every per-set sheet, conditional on the set being played).

Each stage re-solves the same fair targets, so the stage switcher is a true before/after — flip iid @ prior / Set 1 Σ / Length and watch the totals hump move while the handicap holds (clear the Set-1 quotes to see the prior-only pipeline). Best-of-3 and best-of-5 are format configs (all four slams play a 10-point decider tiebreak since 2022); switching loads that format's seed quotes.
↑/↓ steps a field, Shift steps bigger; Malay pairs step their partner the opposite way. The Moneyline and the Set-1 handicap are never fitted — they are cross-anchor consistency readouts (Δ ML / Δ S1hcp); clear either to skip it.
The Set-1 total is the ridge breaker: when present it pins the serve sum per match (stage Set 1 Σ) and the prior becomes a fallback — clear it to see the prior-driven pipeline. Valid inputs: Malay in [−1, +1] non-zero with margin, decimals above 1, serve-sum prior in (1.04, 1.54).
| A=0 | A=1 | A=2 | |
|---|---|---|---|
| H=0 | 17.5 | ||
| H=1 | 16.9 | ||
| H=2 | 40.7 | 24.8 |
| -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | 0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 | +8 | +9 | +10 | +11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.1 | 0.3 | 0.7 | 1.9 | 3.1 | 5.2 | 6.7 | 6.2 | 4.7 | 3.5 | 3.8 | 4.1 | 6.5 | 9.9 | 12.7 | 11.4 | 8.4 | 5.9 | 2.8 | 1.4 | 0.4 | 0.1 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.1 | 0.5 | 1.6 | 3.0 | 5.9 | 7.0 | 8.2 | 7.8 | 5.3 | 8.2 | 6.1 | 1.2 | 3.4 | 4.6 | 3.2 | 3.9 | 4.2 | 4.4 | 5.0 | 4.9 | 3.1 | 2.3 | 2.9 | 1.7 | 0.3 | 0.6 | 0.5 |
| Outcome | Fair | Dec | Priced | Malay |
|---|---|---|---|---|
| Home | 65.6% | 1.525 | 1.492 | +0.49 |
| Away | 34.4% | 2.904 | 2.748 | -0.57 |
How these numbers are computed
| Home | Away | |
|---|---|---|
| Malay quote | +0.82 | -0.95 |
| Decimal | 1.8200 | 2.0526 |
| Implied | 0.5495 | 0.4872 |
| Fair | 0.5317 | 0.4683 |
| Fair decimal | 1.8808 | 2.1353 |
| Over | Under | |
|---|---|---|
| Malay quote | +0.85 | -0.98 |
| Decimal | 1.8500 | 2.0204 |
| Implied | 0.5405 | 0.4949 |
| Fair | 0.5232 | 0.4768 |
| Fair decimal | 1.9114 | 2.0972 |
| Over | Under | |
|---|---|---|
| Malay quote | +0.71 | -0.83 |
| Decimal | 1.7100 | 2.2048 |
| Implied | 0.5848 | 0.4536 |
| Fair | 0.5668 | 0.4332 |
| Fair decimal | 1.7642 | 2.3086 |
| Home | Away | |
|---|---|---|
| Decimal quote | 1.50 | 2.79 |
| Decimal | 1.5000 | 2.7900 |
| Implied | 0.6667 | 0.3584 |
| Fair | 0.6560 | 0.3440 |
| Fair decimal | 1.5245 | 2.9067 |
Standalone strips: Margin — two-way (Power).
Tennis games are not counts: every level is a race with absorbing barriers. A server holding with point probability wins the game with the closed form
(deuce is a geometric race; this run: hold(A) = 0.849, hold(B) = 0.781). A tiny DP walks the tiebreak over the real serve rotation (F, S, S, F, F, …; the 6-6 deuce closes as ), the set lattice alternates servers by game — a set totals {6..10, 12, 13} games, never 11, and a 7-6 counts 13 — and the match folds sets carrying the games margin and total pmfs, the sets joint and every per-set grid. The pre-match serve coin toss is averaged. Everything is exact DP; the engine was validated against a 200k-run Monte Carlo.
The handicap pins the serve difference, but (HDP, OU, ML) leave the serve sum and the form shock τ on a near-flat ridge: a weak-serve iid fit can match all three quotes yet misprice every tiebreak-sensitive tail. Without more quotes the sum comes from the tour/surface prior (1.29 here; ATP ≈ 1.29, WTA ≈ 1.14). With a Set-1 total quote (Saba quotes Set 1), stage Set 1 Σ bisects the sum against it — more serve ⇒ more holds ⇒ longer sets, so P(set-1 over) rises monotonically with Σ — and the prior becomes a fallback (ADR-030 O2: a shape prior upgraded to a per-match reading). The match totals anchor is then consumed by — a 3-node mixture that preserves the sum but compounds over sets — or, only when no Set-1 quote pins Σ, by extra sum when the market runs longer than the iid baseline.
| Stage | Σ = p_A + p_B | Σ src | p_A | p_B | τ | knob | Δ cover | Δ total | Δ S1tot | Δ ML (readout) |
|---|---|---|---|---|---|---|---|---|---|---|
| iid @ prior | 1.2900 | prior | 0.6642 | 0.6258 | 0.0000 | — | 1.4e-7 | 6.4e-2 | 3.3e-2 | 2.75% |
| Set 1 Σ | 1.2469 | S1 | 0.6410 | 0.6059 | 0.0000 | — | 2.6e-7 | 4.7e-2 | 7.2e-7 | 1.58% |
| Length (τ) | 1.2845 | S1 | 0.6620 | 0.6225 | 0.0292 | tau | 1.0e-7 | 4.7e-6 | 2.1e-3 | 0.03% |
The ML — and the Set-1 handicap when quoted — are deliberately not fitting targets: with the sum right they land on their own (Δ ≈ 0 on the seed quotes); a large readout means the book's quotes disagree with each other — a risk signal, not a solver failure.
Unlike basketball quarters, set 3 exists only with probability 41.8% here (set 3 with 41.8%). Per-set markets are void if the set is not played, so their fair prices condition on it — and under the τ mixture the conditioning re-weights the form nodes (deciders come disproportionately from the close-form nodes). Set 1 is unconditional. The same fold also yields the set·set scope for free: the sets joint prices the ML, set handicap and set totals with zero extra parameters.
Full match: P(home wins the set race) off the sets joint — no tie leg. Per set: the set winner, conditional on the set being played (void otherwise). Fair groups are re-margined exactly as in goal-regular (Power ladder two-way, Shin multi-way).
targets tH = powerStrip(HDP pair) tO = powerStrip(OU pair) tS1 = powerStrip(Set-1 OU pair)?
[ML and Set-1 HDP stripped → readouts only]
stage iid : sum = PRIOR(tour/surface) bisect diff until P(A covers | dist) = tH
stage set1 : (with tS1) bisect sum until P(set-1 over | dist) = tS1 — ridge broken
stage length : with tS1 : bisect τ ∈ [0, .06] only (extra sum would contradict the S1 reading)
without : model over > tO ? bisect τ (form mixture — matches shorten)
: bisect sum upward (more serve — matches lengthen)
— the difference re-solves inside every trial (same-targets discipline)
dist: hold(p) closed form → tiebreak DP (serve rotation) → set lattice (alternating serve;
totals ∈ {6..10,12,13}, 7-6 = 13 games) → match fold over (sets, first server)
scopes: ONE dist prices set·set (ML, set HDP/OU) and game·set (match + per-set, conditional)Engine + invariants: docs/src/lib/odds/tennis.ts, __tests__/tennis.test.ts; decision record ADR-030 (extends ADR-029).