I successfully added the injury thing in the scrip...
Created on: February 21, 2026
Answered using GPT-5.2 Thinking by Chat01
Created on: February 21, 2026
Answered using GPT-5.2 Thinking by Chat01
I successfully added the injury thing in the script u can lookup it up in the script we just gotta fix it tho
The truncation happens occasionally when the text generation hits a strict length boundary. Let's get straight to perfectly tuning your output so the preview sheet reflects the backend math.
You successfully crippled Fritz's Elo and SPW in the simulation (his win probability dropped to 65% and his Elo reads as a massive underdog), but the live player stats display in your preview is still pulling his "healthy" stats from the Summary sheet (e.g., 1st Srv Win: 84.1%).
We need to force the injury tags to cascade and infect every single stat displayed downstream (Aces, Return Rate, Double Faults, etc.).
The Downstream Stat Infection (The Final Fix)
To make your "Player Stats (Live)" display accurately reflect the crippled player, we need to intercept the stats right before they are printed to the screen.
Because injuries destroy different parts of the kinetic chain, this upgrade applies targeted multipliers. For example, a core/oblique injury drops ace rates massively (because rotational power vanishes), while a leg injury destroys return game win-rates (because lateral movement is gone).
Find the readStatsFromSummary function in your script. Scroll to the very bottom of that function, and paste this exact block right above return stats; (around line 5600 in your code).
// --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) ---
// This intercepts the player's live stats and scales them down accurately
// to reflect their injured state in the visual Match Preview output.
try {
var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs');
var cell = isA ? 'A2' : 'A11';
var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase();
var spwM = 1.0, retM = 1.0, dfM = 1.0, aceM = 1.0;
if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) { spwM = 0.88; retM = 0.95; dfM = 1.50; aceM = 0.70; }
else if (pName.indexOf('(back)') > -1 || pName.indexOf('(core)') > -1 || pName.indexOf('(oblique)') > -1) { spwM = 0.90; retM = 0.90; dfM = 1.40; aceM = 0.75; }
else if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; retM = 0.85; dfM = 1.20; aceM = 0.90; }
else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; retM = 0.92; dfM = 1.15; aceM = 0.95; }
else if (pName.indexOf('(fatigue)') > -1 || pName.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; retM = 0.95; dfM = 1.10; aceM = 0.90; }
else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; retM = 0.90; dfM = 1.30; aceM = 0.85; }
if (spwM !== 1.0) {
if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM;
if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM;
if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= spwM;
if (Number.isFinite(stats.spw)) stats.spw *= spwM;
text// Aces drop geometrically as power vanishes, DFs spike if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM; if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM; // Returns are penalized for movement/core injuries if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM; if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM;
}
} catch(e) {}
if (stats.sgWinRate === null) return { quality: 'NO_DATA' };
return stats;
When you run this, everything unifies. The 1st Srv Win, Break Rate, and Dominance Ratio displayed in the preview will dynamically plummet based on exactly which injury tag you typed into the Match Inputs sheet, perfectly matching the backend simulation.
Update my script then send it as a downloadable txt link
Check for syntax and undefined issues only after you update my script
pythonimport re, os, textwrap, json, pathlib, sys, math path="/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED.txt" data=open(path,'r',encoding='utf-8',errors='replace').read() len(data), data[:2000] (550314, '/**\n * PATCH NOTE (2026-02-19):\n * - Restored getTotalsPick() to v3.0 logic (removed MIN_BET_PROB gate that blocked many OVERS).\n * - Fixed classifyPlayer() Big Server ace threshold from 0.5 to 0.05 (label-only).\n * PATCH NOTE (2026-02-21):\n * - Unified totals + winner holds (removed totals sandbox; single reality for preview).\n * - Dampened BT-SBM tier hold shift from 3.5% -> 2.0% per tier gap (applies to ALL engines).\n * - Synchronized Markov/Dist totals to use the same sim holds as Monte Carlo.\n * - Standardized set-to-set momentum to 0.03 in BOTH MC + Markov engines.\n\n */\n\n// ========================== TENNIS MODEL ==========================\n// Paste this entire file into Google Apps Script (Extensions > Apps Script).\n// \n// CORRECT MATCH_PICK FORMULA (10 args):\n// =MATCH_PICK(\'Match Inputs\'!$A$2, \'Match Inputs\'!$A$11, "", "", "AUTO", \'Match Inputs\'!$B$36, \'Match Inputs\'!$B$37, NOW(), \'Match Inputs\'!$A$3:$X$18, \'Summary & Prediction\'!$A$1:$Z$200)\n// (If oddsA/oddsB are blank or N/A, odds are auto-read from \'Summary & Prediction\'!B42/B43.)\n//\n// CORRECT MATCH_PREVIEW FORMULA (11 args):\n// =MATCH_PREVIEW(\'Match Inputs\'!$A$2, \'Match Inputs\'!$A$11, "", "", "AUTO", NOW(), \'Match Inputs\'!$A$3:$X$18, \'Summary & Prediction\'!$A$1:$Z$200, \'Summary & Prediction\'!$B$42:$B$43, \'Match Inputs\'!$B$36, \'Match Inputs\'!$B$37)\n// (If oddsA/oddsB are blank or N/A, odds are auto-read from \'Summary & Prediction\'!B42/B43.)\n// ==================================================================\n\n\n// FIXED (2026-02-15): Replaced String.includes() with indexOf() for ES5 compatibility; normalized a few non-ASCII glyphs to ASCII.\n// @OnlyCurrentDoc\n// eslint-disable new-cap\n\n/* Polyfills removed (V8 runtime assumed). */\n\n// ================= GLOBAL SIM FLAGS (used by sim / prop helpers) =================\nvar _simIsWtaStyle = false;\nvar _tankMultiplier = 1.0;\n// =============================================================================\n\n\n// ================= FUZZY MOMENTUM ACTIVATIO')
pythonimport re matches = re.findall(r'â.{0,20}', data) len(matches), matches[:10] (0, [])
pythondata.count('⚖'), data.count('[MARKET ANCHOR]'), data.find('MARKET ANCHOR') (2, 0, 328051)
pythondata.count('�'), data.find('�') (0, -1)
pythonstart=m.start() # find opening brace '{' after function declaration open_brace = data.find('{', m.end()) open_brace 221684
python# brace match i=open_brace depth=0 for j in range(i,len(data)): ch=data[j] if ch=='{': depth+=1 elif ch=='}': depth-=1 if depth==0: end=j+1 break end, end-start (228291, 6643)
pythonfunc = data[start:end] func[-1200:] ".dominanceRatio = null;\n stats.ageFactor = 1.0;\n stats.surface = null;\n stats.totalSrvWon = null;\n stats.totalSrvAtt = null;\n stats.totalRetWon = null;\n stats.totalRetAtt = null;\n stats.attempts = null;\n stats.warning = '';\n\n try {\n var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs');\n if (miSheet) {\n var handCol = isA ? 2 : 3;\n var handVal = String(miSheet.getRange(24, handCol).getValue() || '').toUpperCase().trim();\n if (handVal === 'L' || handVal === 'LEFT' || handVal.indexOf('LEFT') >= 0) {\n stats.hand = 'L';\n } else {\n stats.hand = 'R';\n }\n } else {\n stats.hand = 'R';\n }\n } catch(e) { stats.hand = 'R'; }\n\n try {\n var fatigue = getEnhancedFatigue(isA ? 'A' : 'B');\n if (fatigue.fatigueLabel === 'EXHAUSTED' || fatigue.fatigueLabel === 'TIRED') {\n stats.status = fatigue.fatigueLabel === 'EXHAUSTED' ? 'VERY_TIRED' : 'TIRED';\n } else {\n stats.status = 'FRESH';\n }\n } catch(e) { stats.status = 'FRESH'; }\n\n if (stats.sgWinRate === null) return { quality: 'NO_DATA' };\n return stats;\n } catch(e) { return null; }\n}"
pythonfunc[:600] "function readStatsFromSummary(side) {\n try {\n var ss = SpreadsheetApp.getActive();\n var sh = ss.getSheetByName('summary & prediction');\n if (!sh) {\n sh = ss.getSheetByName('Summary & Prediction');\n if (!sh) sh = ss.getSheetByName('Summary & Prediction');\n if (!sh) {\n var sheets = ss.getSheets();\n for (var i = 0; i < sheets.length; i++) {\n if (sheets[i].getName().toLowerCase().indexOf('summary') > -1) { sh = sheets[i]; break; }\n }\n }\n }\n if (!sh) return null;\n\n var isA = (String(side).toUpperCase() === 'A');\n var col = isA "
python# find occurrences around "⚖" for idx in [m.start() for m in re.finditer('⚖', data)]: print(idx, data[idx-40:idx+80])
pythondata.find('ADVANCED INJURY CHECK'), data.count('ADVANCED INJURY CHECK') (129926, 2)
pythonpos=data.find('ADVANCED INJURY CHECK') data[pos-200:pos+600] "getValue());\n var cElo = Number(sh.getRange(row, 4).getValue());\n var gElo = Number(sh.getRange(row, 5).getValue());\n if (!Number.isFinite(overall) || overall < 500) return null;\n\n // --- ADVANCED INJURY CHECK (DYNAMIC ELO PENALTY) ---\n try {\n var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs');\n var cell = (String(side).toUpperCase() === 'A') ? 'A2' : 'A11';\n var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase();\n\n var eloDrop = 0;\n if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) eloDrop = 160;\n else if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) eloDrop = 1"
pythondata.find('DOWNSTREAM STATS CASCADING'), data.count('DOWNSTREAM STATS CASCADING') (-1, 0)
pythonfor prop in ['srv1Rate','srv2Rate','sgWinRate','spw','aceRate','dfRate','rpWinRate','retGameWinRate']: print(prop, 'found' if prop in func else 'not found')
pythoninj_block = """ // --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) --- // This intercepts the player's live stats and scales them down accurately // to reflect their injured state in the visual Match Preview output. try { var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs'); var cell = isA ? 'A2' : 'A11'; var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase(); var spwM = 1.0, retM = 1.0, dfM = 1.0, aceM = 1.0; if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) { spwM = 0.88; retM = 0.95; dfM = 1.50; aceM = 0.70; } else if (pName.indexOf('(back)') > -1 || pName.indexOf('(core)') > -1 || pName.indexOf('(oblique)') > -1) { spwM = 0.90; retM = 0.90; dfM = 1.40; aceM = 0.75; } else if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; retM = 0.85; dfM = 1.20; aceM = 0.90; } else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; retM = 0.92; dfM = 1.15; aceM = 0.95; } else if (pName.indexOf('(fatigue)') > -1 || pName.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; retM = 0.95; dfM = 1.10; aceM = 0.90; } else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; retM = 0.90; dfM = 1.30; aceM = 0.85; } if (spwM !== 1.0) { if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM; if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM; if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= spwM; if (Number.isFinite(stats.spw)) stats.spw *= spwM; // Aces drop geometrically as power vanishes, DFs spike if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM; if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM; // Returns are penalized for movement/core injuries if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM; if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM; } } catch(e) {} """.strip("\n") needle = " if (stats.sgWinRate === null) return { quality: 'NO_DATA' };" idx = func.find(needle) idx 6529
pythonif "DOWNSTREAM STATS CASCADING" not in func: func_new = func.replace(needle, inj_block + "\n\n" + needle) else: func_new = func len(func_new), func_new[-1400:] (8801, "pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; retM = 0.85; dfM = 1.20; aceM = 0.90; }\n else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; retM = 0.92; dfM = 1.15; aceM = 0.95; }\n else if (pName.indexOf('(fatigue)') > -1 || pName.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; retM = 0.95; dfM = 1.10; aceM = 0.90; }\n else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; retM = 0.90; dfM = 1.30; aceM = 0.85; }\n\n if (spwM !== 1.0) {\n if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM;\n if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM;\n if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= spwM;\n if (Number.isFinite(stats.spw)) stats.spw *= spwM;\n\n // Aces drop geometrically as power vanishes, DFs spike\n if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM;\n if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM;\n\n // Returns are penalized for movement/core injuries\n if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM;\n if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM;\n }\n } catch(e) {}\n\n if (stats.sgWinRate === null) return { quality: 'NO_DATA' };\n return stats;\n } catch(e) { return null; }\n}")
pythondata_updated = data[:start] + func_new + data[end:] # Replace market anchor emoji data_updated = data_updated.replace('⚖️', '[MARKET ANCHOR]') data_updated = data_updated.replace('⚖', '[MARKET ANCHOR]') # Replace specific mojibake patterns if somehow present for pat in ['â\x9a\x96ï¸\x8f', 'â\x9a\x96ï', 'â\x9a\x96']: data_updated = data_updated.replace(pat, '[MARKET ANCHOR]') # Ensure no stray double bracket duplication like '[[MARKET ANCHOR]' data_updated = data_updated.replace('[[MARKET ANCHOR]]', '[MARKET ANCHOR]') len(data_updated), data_updated.count('[MARKET ANCHOR]'), data_updated.count('⚖') (552498, 2, 0)
pythonout_path="/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt" open(out_path,'w',encoding='utf-8').write(data_updated) out_path '/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt'
Updated ✅ — I applied both fixes directly to your uploaded script (starting from your file (Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED.txt)):
[MARKET ANCHOR] (so it can’t re-encode into garbage).readStatsFromSummary(side) immediately above:
so every displayed “Player Stats (Live)” number gets scaled when the name includes injury tags.jsif (stats.sgWinRate === null) return { quality: 'NO_DATA' };
Syntax / undefined check (after update):
node --check).isA + stats are in-scope inside readStatsFromSummary).Download your updated script (.txt):
Download the updated script