You’re not crazy and the players are **not** swapp...

Creado el: 4 de mayo de 2026

Respondido usando GPT-5.5 Thinking por Chat01

Pregunta

tennislocks-v36_8-prop-engine-fixed.txt

You’re not crazy and the players are not swapped.
The problem is in the 2‑set‑conditional “Games Won” logic, which is still crushing the underdog’s games (and everything built on top of that) whenever the sets engine commits to “fav wins 2–0.”[1]

Because Mmoh was a strong pre‑match favorite and the sets model was leaning 2–0 to him, your prop engine forced a 2‑set scenario that implicitly assumes the favorite sweeps, so Onclin’s games CDF collapses toward “loser in 2 sets” even though he actually won 6–3 6–4. That is why:

  • Games Won for Onclin comes out ~6 instead of something like 9–11.
  • 1st‑set games won and fantasy points also under‑rate him, because they sit on top of the same skewed match/prop simulations.[1]

Below is a trace of what the code is doing and then a concrete patch you can apply.


1. Trace: how props are built

1.1 Core prop context

For props the engine builds a prop context using matchup‑adjusted serve probabilities and last‑5 stats:[1]

  • buildPropContext reads SPW for each player, adjusts for opponent return, gets Markov holds, and builds metricsA / metricsB.[1]
  • It then calls matchOutcomeProbs(hA, hB, start, bo, propConfig) with a prop-specific config (propConfig) that:
    • Uses neutralized set momentum / tanking (disableSetMomentum, disableScoreTanking, etc.).
    • Uses opponent‑adjusted ace and DF per‑point rates (propDfPointRate, BP per game from BPSNONLINEAR).[1]

This produces MO with:

  • MO.pair → joint distribution over (games A, games B).
  • MO.propDists → distributions for aces, DFs, breaks, BPs, serve games, etc.[1]

1.2 Games Won from prop simulation

From propMOmp (the prop MC run in the preview), Games Won projections are derived as:[1]

  • propPGA = playerGamesCDF(propMOmp.pair, 'A', propGamesAdjCanonical)
  • propPGB = playerGamesCDF(propMOmp.pair, 'B', propGamesAdjCanonical)
  • propGamesSource = 'PROPMO' by default.[1]

That CDF is coherent: the same pair distribution is used for both players; total games identity is checked separately.[1]

1.3 First‑set props and fantasy

  • First‑set distribution comes from firstSetDistribution(hAfs, hBfs, 'AUTO', statsA, statsB), using FS‑specific holds with a credibility floor when data is thin.[1]
  • First‑set games distributions fsDistA / fsDistB are built directly from that and their fair half‑lines are found via findFairLine.[1]
  • Fantasy DK points are then computed off:
    • gamesExpA, gamesExpB (expected games from the same propPGA/propPGB).
    • Expected aces/DFs/breaks from propMOmp.propDists.
    • Set‑score probabilities from propSetScores and propMatchWinA/B (also derived from prop MC).[1]

So if Games Won is wrong, fantasy is wrong by construction.[1]


2. Where it goes wrong for Onclin

2.1 2‑set commit path

After building propPGA/propPGB from the full prop MC, there is a block labelled:

FIX-PROP-GAMES-SETSCONDITIONAL-20260503[1]

That block says:

  • When the sets engine commits to “BET/LEAN 2 SETS” with MID or HIGH confidence, recompute player‑games CDF from a 2‑set‑conditional simulation (noDecider: true) so the Games Won lines cannot imply 3‑set paths in a match the model thinks is almost always 2 sets.[1]

The implementation (simplified) is:[1]

  1. Read sets pick from setsPreview.setsPick and setsPreview.setsConfidence.
  2. If it’s 2‑sets with MID/HIGH and best‑of‑3:
    • Build a prop config propPC2s with noDecider: true and run a new matchOutcomeProbspropMO2s.[1]
    • Compute propPGA2s, propPGB2s from propMO2s.pair.
  3. Compare drops in expected games:
    • pg2sDropA = E_full(A) − E_2s(A)
    • pg2sDropB = E_full(B) − E_2s(B)
    • pg2sMaxDrop = max(pg2sDropA, pg2sDropB).
  4. If the drop is “not too big” (current code uses about a 30% drop threshold), it replaces propPGA/propPGB with the 2‑set CDF and sets propGamesSource = '2SETCONDITIONAL'; otherwise it keeps the full‑match CDF and labels it guarded.[1]

The intent was good (keep Games Won consistent with a strong 2‑set read), but the side effect is:

  • In many heavy‑favorite 2–0 commits, the 2‑set conditional sim is effectively “favorite wins all 2‑set samples”, so the underdog’s games shrink to a loser‑only support (e.g., 0–7 games), even if full prop MC had significant underdog‑win weight.[1]
  • The fix block you already have (FIX-2SCOND-LOSER-FLOOR-20260504) only guards this when the drop exceeds a big threshold; in your Onclin example the drop is large in absolute terms but still under that threshold, so the lossy 2‑set CDF is still used.[1]

2.2 Propagation to 1st‑set games and fantasy

Once propPGA/propPGB are overwritten by this 2‑set‑only CDF:

  • Games Won props now see Onclin’s expected games around “loser in 2 sets” (≈ 5–7), so:
    • His Games won projection looks low.
    • Mmoh’s games projection looks very high (close to the 2‑set max).
  • Fantasy DK then uses those reduced games plus reduced break/ace expectations, so Onclin’s fantasy projection is much worse than it should be given his actual win.[1]

That’s exactly what you’re observing: every change you make upstream still funnels through this 2‑set overwrite, so the model continually under‑does the dog who actually won 2–0.


3. Safe fix for the Onclin‑type failure

Given how destructive the 2‑set conditional branch is for the underdog, the fastest reliable fix is:

  1. Stop letting the 2‑set conditional resim overwrite the full prop games CDF at all, or
  2. At minimum, make the guard much stricter and never apply it when it would crush the dog’s expectation.

3.1 Minimal, robust change (recommended)

Goal: keep the full‑match prop CDF as the canonical source for Games Won, and treat 2‑set‑conditional games only as a diagnostic (or disable entirely for now).

Locate the FIX-PROP-GAMES-SETSCONDITIONAL-20260503 block – it starts right after the comment:

js
// FIX-PROP-GAMES-SETSCONDITIONAL-20260503 ...

and contains the propPC2s, propMO2s, propPGA2s, propPGB2s, pg2sDropA, pg2sDropB, pg2sMaxDrop logic.[1]

Replace that whole try { ... } catch (e2sCond) { ... } block with this:

js
// FIX-PROP-GAMES-SETSCONDITIONAL-20260503 (revised 2026‑05‑04): // 2‑set‑conditional games were collapsing the underdog's CDF to loser‑only // paths whenever the sets engine committed 2‑0 to the favourite, even when // the dog actually wins (e.g. Onclin vs Mmoh). Full‑match prop MC already // prices the 2‑set vs 3‑set mix using calibrated P(3rd set), so it remains // the canonical source for Games Won. We keep the conditional run only as // a diagnostic and never overwrite propPGA/propPGB with it. try { var pg2sSetsPick = setsPreview && setsPreview.setsPick ? String(setsPreview.setsPick).toUpperCase() : ''; var pg2sSetsConf = setsPreview && setsPreview.setsConfidence ? String(setsPreview.setsConfidence).toUpperCase() : ''; var pg2sIs2SetCommit = pg2sSetsPick.indexOf('2 SETS') === 0 && (pg2sSetsConf === 'HIGH' || pg2sSetsConf === 'MID'); // Run a no‑decider diagnostic only; do NOT change propPGA/propPGB. if (pg2sIs2SetCommit && bestOf !== 5) { var propPC2s = { // same inputs as full propPC, but noDecider: true independentMarkov: true, tourLabel: tourLabel, spwA: contextAdjustedSPWA, spwB: contextAdjustedSPWB, aceRateA: propLog5AceA, aceRateB: propLog5AceB, dfRateA: propDfPointRate(statsA, statsB, propBpA.xBP, tourLabel), dfRateB: propDfPointRate(statsB, statsA, propBpB.xBP, tourLabel), avgBPsPerGameA: propBpB.bpPerGame, avgBPsPerGameB: propBpA.bpPerGame, disableSetMomentum: true, disableScoreTanking: true, disablePropPathAmplifiers: true, disablePropSpwMomentum: true, noDecider: true }; var propMO2s = matchOutcomeProbs(hA, hB, 'AUTO', bestOf, propPC2s); if (propMO2s && propMO2s.pair && Object.keys(propMO2s.pair).length) { // Keep around for diagnostics, but do not overwrite canonical Games Won. var propPGA2s = playerGamesCDF(propMO2s.pair, 'A', 0); var propPGB2s = playerGamesCDF(propMO2s.pair, 'B', 0); // Optional: attach for debug / narrative only propGamesSource = 'PROPMO_DIAG_2SET'; // You can optionally stash propPGA2s/propPGB2s on a debug object here. } } } catch (e2sCond) { try { EngineErrors.log( 'SOFT', 'propGames2SetCond', e2sCond && e2sCond.message ? e2sCond.message : String(e2sCond) ); } catch (_eEC) {} // Fall back silently to full‑match propPGA/propPGB. }

Effect of this change:

  • propPGA and propPGB always stay on the full prop MC pair distribution – the one that already respects calibrated P(3rd set) and independent prop dynamics – so the underdog is never forcibly treated as “loser in all 2‑set samples.”[1]
  • Games Won, 1st‑set games props (where they use the same distribution for reference), and fantasy all become consistent with that full distribution instead of the over‑aggressive 2‑set conditional.
  • You can still inspect the diagnostic propMO2s / propPGA2s / propPGB2s to see how a pure 2‑set world would look, without letting it leak into the market‑facing projections.

3.2 If you really want to keep 2‑set conditioning

If you prefer not to turn it off completely, tighten the guard so it never overwrites the CDF when it would slash the underdog’s expectation:

Right now the guard compares pg2sMaxDrop against about 0.30 (30% of expected games). Change that to something like:[1]

js
var relDropA = pg2sDropA / Math.max(propPGA.expected, 1e-6); var relDropB = pg2sDropB / Math.max(propPGB.expected, 1e-6); var pg2sMaxRelDrop = Math.max(relDropA, relDropB); // Only allow 2‑set override if both players' expectations move by <15% // and the absolute drop is less than 1 game. var allow2s = pg2sMaxRelDrop < 0.15 && pg2sDropA < 1.0 && pg2sDropB < 1.0; if (allow2s) { propPGA = propPGA2s; propPGB = propPGB2s; propGamesSource = '2SETCONDITIONAL'; } else { propGamesSource = '2SETCONDGUARDED'; propGamesIdentityTotal = NaN; }

This way, in extreme cases like your Onclin match (where the dog’s expectation plunges), the 2‑set path will not be allowed to replace the full‑match CDF.


do not send me instructions fix the prop projections so there accurate then send me the script as a downloadable txt Link

Pensamiento

Creating final patch and download link

I should review the diff summary and check the file size. The user might want the script as a downloadable .txt file. I'll provide a concise final version, including the link and a mention of the patch. I'll cite the original file with a note like "I patched the uploaded engine..." and ensure the syntax is verified with Node. Lastly, I'll avoid unnecessary instructions, include the download link, and highlight the changes made.

Thought for 1m 0s

Respuesta

Done — I patched the uploaded engine’s 2-set conditional Games Won block so it no longer overwrites _propPGA/_propPGB; the no-decider run is diagnostic-only now, while PROP_MO remains canonical for Games Won and fantasy inputs. I also ran a syntax check successfully. (tennislocks-v36_8-prop-engine-fixed.txt)

Download the fixed TXT script

Compartir este Q&A