Alright Solomon, I've done a **comprehensive 10-mi...

Creato il: 12 marzo 2026

Risposto usando GPT-5.4 Thinking da Chat01

Domanda

Tennislocks_v17.10.2_3set_suppression_fix.txt

Alright Solomon, I've done a comprehensive 10-minute audit of your file. This script is a 5,800+ line masterpiece of conditional set suppression, but here's what's creating that hidden 3-set collapse in your 55-70% favorites.

DEEP AUDIT: Hidden 3-Set Suppression Mechanisms

PRIMARY CULPRITS (The ones you suspected)

1. _pickTaperSeedA_ - MEDIAN-OF-SOURCES (v17.9.0)

Location: Lines ~300-340
What it does: Takes the median of [stats, Elo, market] instead of picking the most extreme agreeing source.

javascript
// V17.9.0 REFACTOR (Peña et al. §4.2): median-of-sources approach. var sources = [s]; // stats if (Number.isFinite(e)) sources.push(e); // Elo if (Number.isFinite(m)) sources.push(m); // market sources.sort(function(a, b) { return a - b; }); var median = sources[Math.floor(sources.length / 2)];

The problem: In a typical 60% favorite (55-65% edge range):

  • Stats say 62%
  • Elo says 65%
  • Market says 58%

Old behavior would pick 65% (most extreme agreeing) → stronger mismatch index → heavier taper
New behavior picks 62% (median) → weaker mismatch index → lighter taper

But wait - you said more suppression, not less... Let me find where this feeds into the next mechanism.


2. _mismatchIdxFromSeeds_ - THE AMPLIFIER

Location: Lines ~275-315
What it calculates:

mismatch = clamp01((favWp - floor) / range)

Tour-specific floors and ranges:

  • ATP: floor=0.52, range=0.30 → 60% fav = 27% mismatch index
  • WTA: floor=0.50, range=0.28 → 60% fav = 36% mismatch index
  • CH: floor=0.51, range=0.29 → 60% fav = 31% mismatch index

The trap: Even though _pickTaperSeedA_ now returns a lower seed (median vs max), the mismatch index still scales super aggressively in your target range.


3. TAPER_MAXPEN_BOOST - THE HIDDEN MULTIPLIER

Location: Lines ~180-195

javascript
var TAPER_MAXPEN_BOOST_ATP = 0.60; var TAPER_MAXPEN_BOOST_CH = 0.90; var TAPER_MAXPEN_BOOST_ITF = 0.90; var TAPER_MAXPEN_BOOST_WTA = 0.80;

What this does:

javascript
maxPenEff = maxPen * (1 + boost * mismatchIdx)

Example (ATP, 60% favorite):

  • Base TAPER_MAX_HOLD_PEN_ATP = 0.036
  • Mismatch index = 0.27
  • Effective penalty = 0.036 × (1 + 0.60 × 0.27) = 0.0418 hold points

That's 4.2% hold drop for the Set 2 loser. In a 75% hold environment, that's moving from 75% → 70.8%, which collapses 2-1 probability.


4. Between-Set Momentum (SETCONFIDENCEDELTA) - MISSING FROM YOUR FILE

Critical finding: I searched for SETCONFIDENCEDELTA, between-set momentum, set winner boost - NOT FOUND.

What this means: Your diagnosis mentioned:

"Between-set momentum (SETCONFIDENCEDELTA): the set winner gets a small hold boost and the loser a cut, scaled harder in mismatches via moMismatchIdx..."

But the implementation doesn't exist in this script. Either:

  1. It was removed in a refactor
  2. It's in a different version
  3. It's pseudo-code planning that hasn't been built yet

This is GOOD NEWS - you don't have momentum layered on top of taper. You only have taper.


5. Pre-Sim Hold Alignment (wpDivergence > 0.06) - ALSO NOT FOUND

Searched for: wpDivergence, hold alignment, pre-sim alignment
Status: NOT IN THIS FILE

Your diagnosis text says:

"Pre-sim hold alignment: when wpDivergence > 0.06, the engine nudges holds to match a favored win probability..."

But I don't see this code. The only hold adjustments I see are:

  • Taper adjustments (conditional set dependence)
  • Opponent quality adjustments (_adjustStatsByOppQuality_)
  • Tour/surface defaults

SECONDARY MECHANISMS (Still impactful)

6. SET3_TAPER_FRACTION (currently disabled)

Location: Lines ~175-180

javascript
var APPLY_TAPER_TO_SET3 = false; // Decider evidence: do NOT suppress Set 3 length once a decider exists.

Current setting: false - Set 3 is NOT tapered
However, the global multiplier exists:

javascript
var SET3_TAPER_FRACTION = 1.25; // Global multiplier var SET3_TAPER_FRACTION_ATP = 0.85; var SET3_TAPER_FRACTION_CH = 1.25; var SET3_TAPER_FRACTION_ITF = 1.05; var SET3_TAPER_FRACTION_WTA = 0.80;

If this were turned on, Set 3 would get 85% (ATP) of the Set 2 taper → even heavier suppression of 29+ games.


7. _markovEffectiveHolds_ - NO SUPPRESSION (v17.4.6 FIX)

Location: Lines ~220-250

javascript
// INDEPENDENT MARKOV HOLDS (v17.4.6 FIX - 2026-03-01) // Markov is an analytic cross-check and MUST remain independent of Monte Carlo tail governors. // Therefore, Markov uses baseline (pre-taper / pre-tanking) hold probabilities directly.

Status: Correctly isolated - Markov cross-check uses baseline holds, not tapered holds. This is intentional and correct for validation.


THE SMOKING GUN: Where Taper Actually Gets Applied

I couldn't find the actual simulation loop where taper is applied in this excerpt (likely in a function called runMatchSimulation_Exact that wasn't fully captured), BUT based on standard conditional dependence implementations:

Expected flow:

text
Set 1 completes Identify loser Calculate mismatch index (via _mismatchIdxFromSeeds_) Compute taper multiplier: mult = minMult + (1 - minMult) * exp(-lambda * mismatchIdx) Compute hold penalty: maxPenEff = maxPen * (1 + maxPenBoost * mismatchIdx) holdPenalty = maxPenEff * (1 - mult) Apply to Set 2 loser: loserHoldSet2 = clamp(loserHoldSet1 - holdPenalty, holdFloor)

For a 60% ATP favorite:

  • Lambda = 3.4, minMult = 0.15, mismatchIdx = 0.27
  • Multiplier = 0.15 + 0.85 × exp(-3.4 × 0.27) = 0.49
  • maxPenEff = 0.036 × (1 + 0.60 × 0.27) = 0.0418
  • holdPenalty = 0.0418 × (1 - 0.49) = 0.0213 (2.1% absolute drop)

If the underdog had 70% baseline hold:

  • Set 2 hold = 70% - 2.1% = 67.9%
  • This tanks their ability to force a decider

RECOMMENDATIONS

Option A: Soften the taper curve (Conservative)

Target: Reduce suppression in 55-70% favorites while keeping heavy mismatches compressed.

Changes:

  1. Lower TAPER_MAXPEN_BOOST for ATP from 0.60 → 0.35

    • This reduces the mismatch scaling of the penalty
    • 60% favorite would get 3.6% penalty instead of 4.2%
  2. Adjust mismatch index range (ATP):

    javascript
    var _miFloor = 0.52; // unchanged var _miRange = 0.35; // up from 0.30
    • This makes the mismatch index climb slower
    • 60% favorite: 23% index instead of 27%

Expected impact: +4-6% decider probability in 55-70% favorites


Option B: Make taper nonlinear (Advanced)

Target: Aggressive taper in 75%+ favorites, minimal in 55-65% range.

Add a threshold gate:

javascript
function _computeTaperMultiplier_(mismatchIdx, lambda, minMult, softThreshold) { if (mismatchIdx < softThreshold) { // Gentle taper below threshold return 0.85 + (1 - 0.85) * (1 - mismatchIdx / softThreshold); } else { // Aggressive taper above threshold var scaledIdx = (mismatchIdx - softThreshold) / (1 - softThreshold); return minMult + (0.85 - minMult) * Math.exp(-lambda * scaledIdx); } }

Set softThreshold = 0.35 (corresponds to ~68% favorite).

Expected impact: +7-10% decider probability in 55-70%, unchanged in 75%+


Option C: Disable taper entirely for validation (Diagnostic)

Set:

javascript
var USE_CONDITIONAL_SET_TAPER = false;

Run Noskova-Gibson again. If P(3rd set) jumps from 39% → 48-52%, you've confirmed taper is the culprit.


FINAL VERDICT

You ARE being suppressed, but not by the mechanisms you named:

  • SETCONFIDENCEDELTA (momentum): Not in this file
  • wpDivergence (pre-sim alignment): Not in this file
  • Taper base penalty (TAPER_MAX_HOLD_PEN): Present
  • Taper mismatch scaling (TAPER_MAXPEN_BOOST): THIS IS THE KILLER
  • Mismatch index calibration: Aggressive in 55-70% range

The hidden mechanism is the mismatch boost multiplier interacting with the 50/50 OU governor you mentioned. Even though the taper base looks small (3.6%), the boost scales it up to 4.2% in your sweet spot, which is enough to collapse 2-1 outcomes by 8-12%.

Your instinct was right - there IS more suppression beyond the obvious 50/50 line. The fix is to soften the mismatch boost or shift the mismatch index range so 55-70% favorites don't trigger heavy taper.

Here’s a deeper read plus concrete code for both Option A and Option B.

Where the taper actually bites

The heart of the 3‑set suppression is this block inside the Monte Carlo loop, near the bottom of your file. It runs every point, so any bias here is massively amplified.[1]

javascript
// CONDITIONAL SET DEPENDENCE (3-SET TAPER) // V17.9.0: Taper fires symmetrically for ANY Set-1 winner (no fav-only gate). // Reverse taper (asymmetric regression-to-mean penalising underdog Set 1 wins) removed. if (_moUseSetTaper) { if (_isFirstSetFinish && firstSetWinner) { var _adj2 = _applyConditionalTaperToSet2_(dynHA, dynHB, firstSetWinner, _taperGap, _taperTour); dynHA = _adj2.hA2; dynHB = _adj2.hB2; } // Optional: Set 3 taper (Bo3 only) if (_moApplySet3 && bo !== 5 && (setsA === 1 && setsB === 1) && firstSetWinner) { var _apply3 = (firstSetWinner === _taperPreFav) || (_taperGap >= 0.30); if (_apply3) { var _tp3 = _taperParamsForTour_(_taperTour); var _lambda3 = _tp3.lambda; var _minMult3 = _tp3.minMult; var _maxPen3 = _tp3.maxPen; var _set3Frac = _tp3.set3Frac; var _maxPenBoost3 = Number(_tp3.maxPenBoost); if (!Number.isFinite(_maxPenBoost3)) _maxPenBoost3 = 0; var _holdFloor3 = Number(_tp3.holdFloor); if (!Number.isFinite(_holdFloor3)) _holdFloor3 = 0.40; var _mult3 = _threeSetTaperMult_(_taperGap, _lambda3, _minMult3); var _maxPen3Eff = _maxPen3 * (1 + _maxPenBoost3 * _taperGap); var _pen3 = _maxPen3Eff * (1 - _mult3) * SET3_TAPER_FRACTION * _set3Frac; if (_pen3 > 0.30) _pen3 = 0.30; // shave the pre-match underdog in the decider var _dog = (_taperPreFav === 'A') ? 'B' : 'A'; if (_dog === 'A') dynHA = Math.max(_holdFloor3, _clamp01_(dynHA - _pen3)); else dynHB = Math.max(_holdFloor3, _clamp01_(dynHB - _pen3)); } } }

Key points from this and the helpers above it:[1]

  • _taperGap is your mismatch index; by design it is ~0 at ~60% and 1 at ~90%.
  • TAPER_MAXPEN_BOOST_* multiplies the penalty by (1 + boost * _taperGap).
  • _threeSetTaperMult_ converts _taperGap into a nonlinear multiplier between minMult and 1, controlled by lambda.
  • Set‑2 taper fires any time Set 1 finishes (no favorite‑only gate).
  • Set‑3 taper is gated by _moApplySet3 and (firstSetWinner === _taperPreFav || _taperGap >= 0.30) which means your 55–70% band sees decider taper when the pre‑match favorite wins Set 1.[1]

So the real suppression stack is:

  1. _mismatchIdxFromSeeds__taperGap (0 at ~60%, 1 at ~90%).[1]
  2. _taperParamsForTour_ → per‑tour lambda, minMult, maxPen, maxPenBoost, holdFloor.[1]
  3. _applyConditionalTaperToSet2_ and the Set‑3 block above → shave the dog’s hold in Set 2 (always) and in the decider (conditionally).[1]

No SETCONFIDENCEDELTA, no wpDivergence pre‑sim alignment exist in this file; those concepts in your write‑up are older / conceptual, but the live behavior is all taper‑based.[1]


Option A – Softer taper in 55–70% band (minimal surgery)

Goal: keep the mismatch shape but reduce how hard it bites in normal favorite land, especially ATP/WTA.

Step A1 – Soften per‑tour boost

Find this block:[1]

javascript
var TAPER_MAXPEN_BOOST_ATP = 0.60; var TAPER_MAXPEN_BOOST_CH = 0.90; var TAPER_MAXPEN_BOOST_ITF = 0.90; var TAPER_MAXPEN_BOOST_WTA = 0.80;

Replace it with:

javascript
// Softer mismatch scaling: keep heavy mismatches compressed // but reduce suppression in the 55–70% favorite band. var TAPER_MAXPEN_BOOST_ATP = 0.35; // was 0.60 var TAPER_MAXPEN_BOOST_CH = 0.70; // was 0.90 var TAPER_MAXPEN_BOOST_ITF = 0.70; // was 0.90 var TAPER_MAXPEN_BOOST_WTA = 0.45; // was 0.80

This keeps the structure but cuts ~30–40% off the mismatch‑driven extra penalty in your “money zone”.[1]

Step A2 – Stretch mismatch index range

In _mismatchIdxFromSeeds_ you currently have:[1]

javascript
var _miFloor = _miIsWta ? 0.50 : _miIsCh ? 0.51 : _miIsItf ? 0.51 : 0.52; var _miRange = _miIsWta ? 0.28 : _miIsCh ? 0.29 : _miIsItf ? 0.31 : 0.30;

Change to:

javascript
// Stretch range slightly so 55–70% lives lower on the 0..1 mismatch scale. var _miFloor = _miIsWta ? 0.50 : _miIsCh ? 0.51 : _miIsItf ? 0.51 : 0.52; var _miRange = _miIsWta ? 0.33 : _miIsCh ? 0.34 : _miIsItf ? 0.35 : 0.34;

This means a 60% favorite sits further from “1.0 mismatch” and thus sees weaker taper overall.[1]


Option B – Nonlinear taper that mostly leaves 55–65% alone

Goal: heavy taper only kicks hard at ~70–72%+; 55–65% is almost untouched even when Set 1 is lopsided.

Step B1 – Add a helper for nonlinear Set 2 taper

Search for _threeSetTaperMult_ (wherever it is defined; the Set‑3 code is already using it). Immediately after that definition, add:[1]

javascript
/** * Nonlinear taper multiplier for Set 2. * - Below softThreshold: very gentle compression (protects 55–65% favorites). * - Above softThreshold: original exponential shape. */ function _twoSetTaperMultSoft_(mismatch, lambda, minMult, softThreshold) { mismatch = Number(mismatch); lambda = Number(lambda); minMult = Number(minMult); softThreshold = Number(softThreshold); if (!Number.isFinite(mismatch) || mismatch < 0) mismatch = 0; if (mismatch > 1) mismatch = 1; if (!Number.isFinite(lambda) || lambda <= 0) lambda = 3.0; if (!Number.isFinite(minMult) || minMult < 0) minMult = 0.15; if (!Number.isFinite(softThreshold) || softThreshold <= 0 || softThreshold >= 1) softThreshold = 0.35; // Below threshold: gently slide from 1.0 toward ~0.85 as mismatch → softThreshold. if (mismatch <= softThreshold) { var t = mismatch / softThreshold; // 0..1 var hi = 1.0; var lo = 0.85; // mild compression only return hi - (hi - lo) * t; } // Above threshold: rescale mismatch into [0,1] segment and apply the original curve. var scaled = (mismatch - softThreshold) / (1 - softThreshold); if (scaled < 0) scaled = 0; if (scaled > 1) scaled = 1; var raw = minMult + (1 - minMult) * Math.exp(-lambda * scaled); if (raw < minMult) raw = minMult; if (raw > 1) raw = 1; return raw; }

This gives you a “soft” version for Set 2 only.[1]

Step B2 – Use the soft multiplier in Set‑2 taper

Find where _applyConditionalTaperToSet2_ computes its multiplier / penalty. It will look similar to the Set‑3 block, using _threeSetTaperMult_(_taperGap, lambda, minMult) and maxPen * (1 + maxPenBoost * _taperGap). In that function, replace the multiplier line with something like:

javascript
// OLD (conceptually) // var mult2 = _threeSetTaperMult_(mismatch, lambda, minMult); // NEW: protect 55–65% band with softThreshold ≈ 0.35 (≈68–70% favorite) var softThreshold = 0.35; var mult2 = _twoSetTaperMultSoft_(mismatch, lambda, minMult, softThreshold);

Leave the rest of the function intact, including maxPenEff and hold floors.[1]

This change means:

  • For _taperGap below 0.35, Set‑2 loser is only shaved a little, preserving 2‑1 routes.
  • For _taperGap above 0.35, you get essentially the old, strong taper behavior.

Option C – Diagnostic toggle (quick sanity check)

You can temporarily prove to yourself that taper is the suppression source by flipping the master flag.[1]

javascript
var USE_CONDITIONAL_SET_TAPER = true;

Temporarily set:

javascript
var USE_CONDITIONAL_SET_TAPER = false;

Run Noskova–Gibson and a couple of your “feels too straight‑setty” 60–65% favorites and note:

  • P(decider)
  • Distribution of 2‑0 vs 2‑1
  • Totals OU leaning vs market line

Then turn it back on and apply Option A/B.


How this interacts with your sets / totals governors

Downstream, the sets and totals pipes are reading the simulated set scores and P(decider), but they do not add extra 3‑set suppression themselves; they just blend model / market / hold priors and cap shifts.[1]

  • _computeDeciderSetProb_(sim, bestOf) just reads the simulated set histogram.[1]
  • _setPathFromSim_ builds the 2‑0 / 2‑1 probabilities from the same histogram.[1]
  • The sets O/U and exact‑score blocks then apply bounded market/hold shifts (with SHIFTCAPs and WARN_GAPs) – they can nudge, but they are not killing 3‑set mass on their own.[1]

So the only place you should relax 3‑set suppression is the taper layer; touching sets/totals governors would be double‑counting.


Update my script then send it as a Downloadable txt link

Pensiero

bash -lc diff -u /mnt/data/Tennislocks_v17.10.2_3set_suppression_fix.txt /mnt/data/Tennislocks_v17.10.2_3set_suppression_fix_UPDATED.txt > /mnt/data/Tennislocks_v17.10.2_3set_suppression_fix_UPDATED.patch || true && ls -l /mnt/data/Tennislocks_v17.10.2_3set_suppression_fix_UPDATED.*

Thought for 3m 7s

Risposta

Done — I patched your script and saved the updated .txt file based on the uploaded source (Tennislocks_v17.10.2_3set_suppression_fix.txt).

Updated script: Tennislocks_v17.10.2_3set_suppression_fix_UPDATED.txt

Optional diff: Tennislocks_v17.10.2_3set_suppression_fix_UPDATED.patch

Applied changes:

  • softened TAPER_MAXPEN_BOOST_*
  • added Set-2 soft-threshold constants
  • widened mismatch ranges
  • added _twoSetTaperMultSoft_()
  • switched _applyConditionalTaperToSet2_() to the softer Set-2 curve
  • propagated the softer mismatch scaling into _computeMismatchGap_() and diagnostics

I also ran a syntax check on the updated script successfully.

Condividi questa Q&A