I successfully added the injury thing in the scrip...

Created on: February 21, 2026

Answered using GPT-5.2 Thinking by Chat01

Question

Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED.txt

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.).

  1. Fix the "Market Anchor" Mojibake
    That weird ⚖️ symbol happens when Google Apps Script tries to read the ⚖️ emoji but gets its text encoding scrambled.
  • Press CTRL + F inside your script.
  • Search for: ⚖ï
  • Replace it perfectly with: ⚖️ (or just type [MARKET ANCHOR] so it never breaks again).
  1. 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

Python

python
import 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')

Python

python
import re matches = re.findall(r'â.{0,20}', data) len(matches), matches[:10] (0, [])

Python

python
data.count('⚖'), data.count('[MARKET ANCHOR]'), data.find('MARKET ANCHOR') (2, 0, 328051)

Python

python
data.count('�'), data.find('�') (0, -1)

Python

python
start=m.start() # find opening brace '{' after function declaration open_brace = data.find('{', m.end()) open_brace 221684

Python

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)

Python

python
func = 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}"

Python

python
func[: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

python
# find occurrences around "⚖" for idx in [m.start() for m in re.finditer('⚖', data)]: print(idx, data[idx-40:idx+80])

Python

python
data.find('ADVANCED INJURY CHECK'), data.count('ADVANCED INJURY CHECK') (129926, 2)

Python

python
pos=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"

Python

python
data.find('DOWNSTREAM STATS CASCADING'), data.count('DOWNSTREAM STATS CASCADING') (-1, 0)

Python

python
for prop in ['srv1Rate','srv2Rate','sgWinRate','spw','aceRate','dfRate','rpWinRate','retGameWinRate']: print(prop, 'found' if prop in func else 'not found')

Python

python
inj_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

Python

python
if "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}")

Python

python
data_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)

Python

python
out_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'

Thought

Providing the updated script

There were no runtime load issues, so I’m now ready to provide the user with the updated script. I'll include a link like [Download the updated script](sandbox:/mnt/data/...) and cite the original file with a mention of the modifications made, like replacing the Market Anchor emoji with [MARKET ANCHOR], and inserting the downstream injury block into readStatsFromSummary.

Checking consistency with sgWinRate

I’ve noticed some slight confusion about sgWinRate and stats.sgWinRate in the user’s inserted block. The user’s block uses both stats.sgWinRate and stats.spw. It's fine, though, since both seem consistent. Ultimately, they used sgWinRate correctly, with the intended check in their original code.

Verifying block consistency and safeguarding miSheet

The user’s block properly adjusts sgWinRate and spw, which matches our changes. I ensured we haven't disrupted earlier checks for fatigue. While we did insert before the try block, the use of miSheet should be safe given it's caught in case of an issue. Now it's time to finalize the update and provide the download link along with a concise diff of modifications.

Thought for 4m 33s

Answer

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 mojibake hardening: replaced the ⚖️ marker with [MARKET ANCHOR] (so it can’t re-encode into garbage).
  • Downstream stat infection: injected your ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) block into readStatsFromSummary(side) immediately above:
    js
    if (stats.sgWinRate === null) return { quality: 'NO_DATA' };
    so every displayed “Player Stats (Live)” number gets scaled when the name includes injury tags.

Syntax / undefined check (after update):

  • ✅ Passes a JS syntax parse check (node --check).
  • ✅ No new undefined identifiers introduced by the inserted block (isA + stats are in-scope inside readStatsFromSummary).

Download your updated script (.txt):
Download the updated script

Share this Q&A