You’re not crazy and the players are **not** swapp...
Creato il: 4 maggio 2026
Risposto usando GPT-5.5 Thinking da Chat01
Creato il: 4 maggio 2026
Risposto usando GPT-5.5 Thinking da Chat01
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:
Below is a trace of what the code is doing and then a concrete patch you can apply.
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]matchOutcomeProbs(hA, hB, start, bo, propConfig) with a prop-specific config (propConfig) that:
disableSetMomentum, disableScoreTanking, etc.).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]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]
firstSetDistribution(hAfs, hBfs, 'AUTO', statsA, statsB), using FS‑specific holds with a credibility floor when data is thin.[1]fsDistA / fsDistB are built directly from that and their fair half‑lines are found via findFairLine.[1]gamesExpA, gamesExpB (expected games from the same propPGA/propPGB).propMOmp.propDists.propSetScores and propMatchWinA/B (also derived from prop MC).[1]So if Games Won is wrong, fantasy is wrong by construction.[1]
After building propPGA/propPGB from the full prop MC, there is a block labelled:
FIX-PROP-GAMES-SETSCONDITIONAL-20260503[1]
That block says:
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]
setsPreview.setsPick and setsPreview.setsConfidence.propPC2s with noDecider: true and run a new matchOutcomeProbs → propMO2s.[1]propPGA2s, propPGB2s from propMO2s.pair.pg2sDropA = E_full(A) − E_2s(A)pg2sDropB = E_full(B) − E_2s(B)pg2sMaxDrop = max(pg2sDropA, pg2sDropB).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:
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]Once propPGA/propPGB are overwritten by this 2‑set‑only CDF:
Games won projection looks low.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.
Given how destructive the 2‑set conditional branch is for the underdog, the fastest reliable fix is:
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]propMO2s / propPGA2s / propPGB2s to see how a pure 2‑set world would look, without letting it leak into the market‑facing projections.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]
jsvar 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.
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)