Updating user script to separate engines
The user provided a script and requested changes. They want to ensure the engines (MC, Markov, Cluster) stay independent, especially removing Markov's influence on MC. The MC should use baseline holds, not Markov adjustments, and Markov should solely compute totals with baseline holds. I'll update the script to reflect these adjustments.
Adjusting Markov and MC simulation structure
The user script originally used _markovEffectiveHolds_ in a way that affects independence, as it incorporated mismatch taper. To fix this, I'll remove the _markovEffectiveHolds_ call from both the MC and Markov engines, keeping MC’s existing taper setup. I'll ensure Markov doesn’t incorporate tanking and adjusts correctly.
Refining Markov and MC hold handling
For the MC simulation, I'll pass baseline sim holds directly, including any schedule trust adjustments, while keeping the conditional taper intact. Markov should only use baseline holds without mismatch taper, so I'll remove _markovEffectiveHolds_. The Governor needs to compare raw MC outputs with Markov before blending, ensuring clear separation.
Checking cluster and Markov independence, updating script
To maintain independence, I'll use base holds for Markov and ensure that cluster doesn’t merge with Markov. I’ll verify if the cluster uses Markov totals or _markovEffectiveHolds_ and adjust accordingly. I'll update the script, run a syntax check, and confirm no undefined variables using a static analysis tool like Node.
Updating script and checking for errors
To update the script, I'll first search for _markovEffectiveHolds_ in the code, identify locations to modify, and replace it with baseline holds where needed. I'll also ensure the environment parses JavaScript correctly, using minimal checks like regex for basic errors. After editing, I'll update the comments, save the new file, and provide a link to the user.
Checking for syntax and undefined issues
The script uses SpreadsheetApp functions without defining them, which would cause errors. I’ll check for syntax issues by parsing the code with Node. For undefined variables, I’ll scan for identifiers that aren’t defined and search for any "ReferenceError" patterns. I'll use Python to read the file and identify those occurrences.
from pathlib import Path, PurePosixPath
path = Path("/mnt/data/Tennislocks_V17.4_Cluster_Governor_v17.4.5_FIXED.txt")
text = path.read_text(encoding="utf-8", errors="ignore")
len(text), text[:2000]
Searching for markovEffectiveHolds usages
I need to search for all occurrences of _markovEffectiveHolds_ in the code to see where it's used. This will help identify where I need to make changes, ensuring that it’s correctly adjusted or removed depending on its function within the script. Time to search!
import re, textwrap, itertools, math
occ = [m.start() for m in re.finditer(r"markovEffectiveHolds", text)]
len(occ), occ[:10]
Locating occurrences of markovEffectiveHolds
There are 8 occurrences of _markovEffectiveHolds_; one is a function definition around 26k, which I’ve already seen. The others are function calls. I’ll need to check the surrounding context of each of these calls to understand how it’s being used and make the necessary changes.
def context(pos, window=600):
start=max(0,pos-window); end=min(len(text),pos+window)
return text[start:end]
for i,p in enumerate(occ):
print("\n---", i, p, "---")
print(context(p, 400)[:800])
Reviewing function calls and necessary changes
The major call at 736278 pre-shaves holds, which is exactly what the user mentioned, so it needs to be removed. Other calls at 625899, 626887, and 672539 incorrectly use _markovEffectiveHolds_ where pure holds should be used in the Markov engine. I’ll either revise them to call matchOutcomeProbs directly or create a new function for calculating holds without taper. The last batch, calls at 760756, 767748, and 810220, deal with mismatch decider alignment and cluster governor. These should be adjusted to read match shape independently.
Let's extract a bigger chunk around 736278 to understand totals-only sim function
chunk = context(736278, 2000)
print(chunk[:2000])
print(chunk[2000:4000])
Planning script adjustments for Markov inputs and simulation holds
To fix things, I’ll remove _effSIM = _markovEffectiveHolds_ and replace it with { hA_eff: simHA, hB_eff: simHB }—but actually, it's better to just remove it entirely. I'll keep the simHA/simHB as simulations already handle conditional taper. Also, since the user wants pure Markov inputs, I’ll ensure functions like matchOutcomeProbs use baseline holds directly. Adjustments are needed for various call sites that currently use _markovEffectiveHolds_. I might deprecate or rename that variable for clarity.
Decoupling mismatch computation from Markov and cluster
I think we might still need the mismatch index for tapering, as it’s involved in in-simulation tapering. The mismatchIdx could be related to _mismatchIdxFromSeeds. However, since the user mentioned that the cluster shouldn't merge with Markov, I need to decouple the totals cluster governor from the Markov-generated mismatch. I'll focus on inspecting call sites 810220 and 760756, likely in getTotalsPick or the totals governor function. Time to dive into the details around 810220.
print(context(810220, 2500)[:2500])
print(context(810220, 2500)[2500:5000])
Isolating Markov math from tapering logic
The block for computing Markov totals distribution needs adjustments. To keep Markov math clean, we should use baseline hold estimates (hA_base, hB_base) instead of values like simHA/simHB, which might include class shifts or schedule trust. These baselines are what Markov and simulation engines should both share, ensuring consistency in skill estimates while maintaining independent game mechanics modeling. The adjustment ensures no sharing of tapering or blowout logic.
Refining mismatch and Markov logic
We should use simHA_base and simHB_base as baselines after class and schedule shifts but before pre-sim taper. Inside the Markov block, we need to eliminate any calls to markovEffectiveHolds and calculate mismatch separately. Mismatch shrink should also be removed from the Markov decider to keep it pure. For the governor, mismatch can be computed but shouldn't influence Markov outputs directly. Under-score penalties may be better handled via MC distribution instead of Markov, although confidence cross-checks might still utilize Markov but avoid flipping sides.
Clarifying Markov outputs for governance
The governor can use Markov outputs and disagreements, but the Markov engine should output pOver and fairLine based on pure holds. pDecider should be kept pure as well, with no mismatch shrink applied. We can use matchOutcomeProbs for a diagnostic without penalties. For under-score penalties, we might prefer using MC pDecider instead of Markov to retain independence.
Reconsidering cluster’s role as governor
It seems like the cluster is acting as a governance layer between the MC and Markov engines, possibly making decisions based on their outputs. In the getTotalsPick, both distributions are calculated and compared, with the cluster governing the final choice. To avoid confusion, we should modify the code so that markovEffectiveHolds is not used in totals or winner decisions, possibly keeping the function for legacy purposes but not calling it.
Implementing user-requested changes
I'll restore Markov to pure and ensure MC uses its baseline. The cluster engine will use MC simulation outputs. As for the ORP tone-down request: while the user wants these conceptual changes, I’ll implement guardrails like capping MAX ORP move per stat. Their instruction to "update my script" suggests this should be done.
Unifying ORP adjustments and trust
We'll add an optional maxMove parameter to limit stat changes, capping the difference from raw to adjusted. This will ensure the moves stay within the proposed limits. I'll also adjust sampleConf to incorporate sg better, lowering the floor to 0.15. For the ORP tone, we’ll implement strong adjustments only when there’s a trust gap. We may adjust trust towards 1.0 if both are low.
Updating adjustments for ORP, direction, and symmetry
We'll modify the function to pass opponent trust with minimal disruption to call sites. We can adjust directional stats lightly and cap ORP's impact on confidence. To implement the symmetry guard, I'll adjust the move size based on trust and game size. Additionally, we’ll decouple architecture and ensure cluster compares raw outputs, not blended ones.
import re
p_over_blend = [m.start() for m in re.finditer(r"pOverBlend", text)]
len(p_over_blend), p_over_blend[:20]
Inspecting the "getTotalsPick" function
I want to explore the area around positions like 808k and 810k to understand how the totals pick function is affecting things. I'll look for the getTotalsPick function to see where decisions are being made, then inspect those areas more closely around positions like 818700.
m = re.search(r"function getTotalsPick(", text)
m.start() if m else None
Examining totals pick and related logic
I’m going to focus on a section around 568321 to check the logic for totals pick. Since the file is huge, I’ll start by looking at the relevant portion starting from around 790k. I need to find where variables appear and trace them, especially around the 808xxx positions. I'll also check how blending and the cluster governor are used later, near 820k.
Let's get chunk around 818700
print(context(818700, 2200)[:2200])
print(context(818700, 2200)[2200:4400])
Adjusting gap metrics and direction logic
It looks like the gap metrics are already computed, but there’s an issue with blending pOver values between MC and Markov. The correct approach is to compute the gap between MC and Markov directly, not their blended form. We should also reconsider using pOverEval for direction, focusing on MC and Markov separately for comparison instead. I'll look into where pOverBlendEval is computed to adjust it properly.
Get around 813700 where pOverBlendEval might be set
print(context(813780, 900)[:900])
print(context(813780, 900)[900:1800])
Adjusting blending logic for direction and gap computation
It seems computeTotalsBlend mixes MC and Markov for diagnostics but ends up blending for direction, which goes against the user’s request. They want direction based on raw outputs. To fix this, I should use MC for the gap computation and adjust direction and pick using MC as the primary, with Markov as a cross-check.
Clarifying blending logic for direction and metrics
I’ll set pOverEval to MC’s no-push probability for decision-making and still compute the blend for shape metrics like methodDisagree. Though the user wants MC for direction and Markov only for disagreements and confidence adjustments, I’ll keep blending for diagnostics and shape metrics but ensure methodDisagree uses raw MC and Markov.
Locate computeTotalsBlend function definition
m = re.search(r"function computeTotalsBlend(", text)
m.start() if m else None
print(context(440096, 2000)[:2000])
print(context(440096, 2000)[2000:4000])
print(context(440096, 2000)[4000:6000])
print(context(440096, 6000)[4000:8000])
Continue further down from 440096+6000 to capture rest of computeTotalsBlend
start=440096
print(text[start+6000:start+9000])
Reviewing function boundaries and code snippet
It looks like we missed part of the computeTotalsBlend function because our snippet started too late, jumping past some critical code. We should slice from 440,096 to around 443,000 to capture the missing content before we entered other functions. Let’s locate the "Tour-spe" reference and adjust accordingly.
idx = text.find("// Tour-spe", 440096, 446096)
idx
Adjusting code snippet boundaries
Let's extract the section from 441,900 to 443,000 to ensure we capture the rest of the computeTotalsBlend function. This should help us gather everything we missed earlier, allowing us to complete the context before jumping into the next functions.
print(text[441900:443800])
Revising computeTotalsBlend usage
We'll keep computeTotalsBlend as-is for diagnostics or shape metrics but prevent it from influencing direction settings. In the totals calculation, we'll set the direction pOverEval to MC and use blending only for diagnostics. The gap distinction will use the absolute difference between MC and Markov. We’ll refine Markov outputs and ensure they're independent from the blended elements. There's still uncertainty about whether cluster decides when Markov is missing; I'll check further at line 760,756.
print(context(760756, 2200)[:2200])
print(context(760756, 2200)[2200:4400])
Considering decider alignment correction with Markov
The decider alignment correction uses a Markov-based mismatch logic, which could conflict with our goal of keeping Markov pure. If it's part of the projectedGames calculation for winner prediction, it’s worth reconsidering, especially since it might contribute to unwanted compression. We could either adjust it or remove the use of MarkovEffectiveHolds, possibly using pure Markov for cross-checking instead.
Reevaluating the post-sim mismatch suppression correction
The correction affects the MC totals output, impacting the cluster's decisions. It works by suppressing blowouts, but this feels redundant — it's essentially another layer of compression. Since the user wants to avoid compression and enhance sovereignty, it might be best to remove this correction or redesign it to avoid relying on Markov-Affected Holds.
Removing the circular dependency correction
To ensure independence and avoid double counting, I’d remove or disable the correction that relies on Markov to modify MC outputs — it undermines the system’s independence. The user wants each engine independent, so using Markov this way creates a circular reference that complicates things. I'd suggest purely using MC internal metrics instead.
Fixing or disabling 3-set tail correction
The intention seems to fix an inflated 3-set tail, but MC already handles tapering, so this correction isn't necessary. I can disable the correction code by setting it to zero or gating it behind a flag, ensuring independence. Default to "false" for the flag, and add it for future flexibility.
print(context(767748, 1800)[:1800])
print(context(767748, 1800)[1800:3600])
Modifying preview to display pure Markov probabilities
I need to adjust the preview output to display pure Markov probabilities, removing the reliance on adjusted values like MarkovEffectiveHolds. For display purposes, I'll show the MC decider percentage and the pure Markov decider separately. Adjustments like _p3setAdj may not be necessary here.
Ensuring proper MC-only adjustments and fixing Markov interference
I need to adjust calculations to ensure values are computed using MC rather than Markov, especially for totals and p3 adjustments. I’ll check any calls in FAIR_TOTAL_GAMES and ensure no Markov interference, avoiding pre-crushing holds. I'll search for and update any instances where markovEffectiveHolds is used. Some functions might be unnecessary, but I'll keep them for compatibility.
Renaming and deprecating markovEffectiveHolds function
The user clarified that the cluster isn't merged with Markov, so we might keep the function but rename it to _taperEffectiveHolds_ and remove its call sites. This would make the function become obsolete, so it would be better to mark it as deprecated for historical purposes. I'll proceed with renaming it.
Considering function renaming and impact on external formulas
Changing the function name could break other formulas, especially sheet functions like EXPECTED_TOTAL_GAMES() and FAIR_TOTAL_LINE(), as they depend on _markovEffectiveHolds_. I should update those references. However, if the user directly used the old name, we can keep it as a wrapper for the new function to avoid confusion.
Implementing function renaming and adjustments
The main goal is to end Markov usage here, so I'll introduce _taperProxyEffectiveHolds_ and make _markovEffectiveHolds_ an alias, marking it as deprecated. We’ll keep the original function but update its comment and ensure it’s not used. We’ll also adjust related MC pass and Markov calls accordingly, focusing on using the proper metrics for direction.
find occurrences of "pOverEval" near totals pick; list some indices
occ_eval = [m.start() for m in re.finditer(r"pOverEval", text)]
len(occ_eval), occ_eval[:20], occ_eval[-10:]
print(context(820450, 1200)[:1200])
print(context(821000, 1800)[:1800])
Adjusting getTotalsPick behavior
We’re aiming to ensure that getTotalsPick uses only MC data, while still respecting the empirical anchor for adjustments. We'll pass the MC no-push for direction decisions, but need to set _totCtx properly for cross-checks and diagnostics. I’ll examine the getTotalsPick function to ensure these alignments are correctly implemented.
start = m.start()
end = start + 120000 # 120k chars
sub = text[start:end]
for pat in ["gapP", "pOverMarkov", "wMarkov", "fairLineMarkov", "methodDisagree"]:
print(pat, sub.find(pat))
Verifying getTotalsPick signature
I noticed that pOverMarkov appears at position 51 in getTotalsPick, indicating the function might expect parameters like pOverEval and ctx. I’ll check the start of the getTotalsPick function to confirm its signature and how it handles inputs. This will help clarify how we pass data through the function.
print(text[start:start+1500])
Refining function search
I realized I made a mistake—getTotalsPick is not where I thought (it's actually computeTotalsBlend). I seem to have overwritten my search result or made a mix-up when I printed the text. I’ll redo the search to make sure I’m targeting the correct function and not something I accidentally replaced earlier.
m_gt = re.search(r"function getTotalsPick(", text)
m_gt.start(), text[m_gt.start():m_gt.start()+200]
Printing beginning of getTotalsPick
Alright, now I’ll print the beginning of getTotalsPick to verify its contents. This should help clear up any previous confusion, allowing me to check things more clearly and ensure I'm on the right track with the correct function.
start = m_gt.start()
print(text[start:start+2000])
Refining getTotalsPick logic
getTotalsPick currently expects a combined output with both MC and Markov. I need to adjust so it only uses MC for pOverNoPush. Also, any logic that references blended pO should be updated. I’ll look for "pO" usage inside getTotalsPick to ensure the directional decision logic is correct.
sub = text[start:start+120000] # maybe 120k
count occurrences of 'pO ' or 'pO.' but to find uses
import re
uses = [(m.start(), sub[m.start()-30:m.start()+50]) for m in re.finditer(r"\bpO\b", sub)]
len(uses), uses[:5]
Inspecting getTotalsPick for pO usage
I want to explore more of getTotalsPick to check how pO is influencing decisions, like thresholds. I think it might go beyond 120,000 lines. I'll look through the sub-function for references to conditions like "pO >= " or "if (pO" to understand how it impacts logic.
for pat in ["if (pO", "pO >", "pO <", "pO =", "pO)", "pO;"]:
print(pat, sub.count(pat))
Extracting pO usage beyond initial parse
Next, I’ll extract the relevant section where pO is used after the initial parse. I'll locate the first occurrence beyond the one I’ve already checked. By using uses[10] or similar references, I can focus on the part where pO is further involved. Let’s see how it plays out.
for idx,(pos,snip) in enumerate(uses):
if idx in [5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]:
print(idx, pos, snip.replace("\n","\n")[:120])
Adjusting function for MC usage and consistency
getTotalsPick uses pO for direction and scoring, and passing MC pOverNoPush as pO aligns with the requirement for MC to be primary. However, there are still references to blended logic in the comments. I'll update these comments and variables accordingly. The function uses pMarkov and pMC too, so I'll keep an eye on any discrepancies. I'll make sure ctx.pOverMC is set and used properly, so it improves consistency. Also, the "soft fence" section using pO will remain unchanged.
Ensuring no blended computation for gap in getTotalsPick
I need to make sure we don't compute "gap" based on the blended data anymore. It might be inside getTotalsPick, so I'll look for where agreeSide is computed and references pMC. I'll inspect the code around position 2200 in the subroutine to confirm it's all using MC.
print around 2200-5200 in getTotalsPick
print(sub[2000:5200])
Clarifying changes to Markov and MC usage in getTotalsPick
The fragile calculation and side parts are fine with pO as MC, but ctx.gapP must use both MC and Markov. I’ll update the logic and ensure consistency with both. I'll also update getTotalsPick’s comment to reflect MC as primary, with Markov in ctx for governance. We’ll adjust the Markov distribution computation too and update any applicable functions. Also, need to sync Markov and Monte Carlo holds correctly.
Finalize blending logic and getTotalsPick adjustments
We'll keep the same logic, no taper. The cluster isn't currently merged with Markov, but computeTotalsBlend does blend, which may remain optional. Yet, since getTotalsPick says pOverNoPush is combined, we might eliminate blending. I'll verify the use of blending details like _totCtx.leftTail and associated symmetry. Then, we can finalize if it's still needed for confidence tier adjustments.
Finalizing direction logic and updating blending usage
To align with the new architecture, we'll avoid using blending to decide direction but might still use wMarkov for adjusting grades. We'll keep computeTotalsBlend for disagreement metrics but not for direction. I'll ensure no further code uses pOverBlendEval to determine picks and limit its use for diagnostics.
search in getTotalsPick substring for "pOverBlend"
sub_gt = text[m_gt.start():m_gt.start()+120000]
sub_gt.find("pOverBlend"), sub_gt.count("pOverBlend")
Modifying blending logic and adjustments for Markov
We’re not using pOverBlend in getTotalsPick, which is good. I’ll keep computeTotalsBlend for diagnostics and shape metrics, but not pass pOverBlend. For the diagnostics site, we’ll update pOverBlendEval to pOverGames and store pOverBlend separately. I'll also modify the Markov distribution and probability adjustments accordingly.
Refining Markov inputs and removing unnecessary shaving
Pre-shaving holds via markovEffectiveHolds needs to be removed, which is done. I’ll now ensure Markov inputs are pure, without importing taper knobs or hold floors. I suspect matchOutcomeProbs might include taper due to opts; I’ll check the function definition to confirm and refine.
m_mo = re.search(r"function matchOutcomeProbs(", text)
m_mo.start(), text[m_mo.start():m_mo.start()+200]
Inspecting matchOutcomeProbs for taper
I’ll quickly check the matchOutcomeProbs function to ensure it doesn't incorporate taper or blowout suppression, especially since it could be part of Markov recursion. I'll search within it for terms like "TAPER" or "mismatch" to make sure nothing unwanted is being applied there.
mo_sub = text[m_mo.start():m_mo.start()+30000]
for pat in ["TAPER", "taper", "mismatch", "winProbA", "propConfig"]:
print(pat, mo_sub.lower().find(pat.lower()))
Disabling taper logic in matchOutcomeProbs
It looks like matchOutcomeProbs includes taper logic through propConfig with winProbA and others. That's coupling the logic, so to keep Markov pure, we must disable taper logic when used for the cross-check. I’ll check the places where mismatch corrections are applied to make sure they don't interfere.
Show snippet around where taper mentioned in matchOutcomeProbs
pos = 276133 + 1500
print(text[pos:pos+2200])
Clarifying matchOutcomeProbs vs. runMatchSimulation
Hmm, this snippet doesn't seem to belong to matchOutcomeProbs. It looks more like part of runMatchSimulation, which includes Monte Carlo iterations. I need to ensure that for the Markov cross-check, I use a function free from set correlation and conditional taper. I'll check for the disableSetMomentum flag.
import re
opts = sorted(set(re.findall(r"propConfig.([A-Za-z0-9_]+)", mo_sub)))
opts[:40], len(opts)
Ensuring a pure Markov engine for user requirements
Looks like matchOutcomeProbs includes set-to-set momentum and mismatch adjustments, which blend things together. But the user wants a pure Markov engine, independent sets, constant holds, and no momentum. I’ll check the function to see if it's already tailored for that or if we need an older version to meet the requirements.
print(text[m_mo.start():m_mo.start()+6000])
Identifying the issue with matchOutcomeProbs function
It seems like matchOutcomeProbs is actually a Monte Carlo simulation, not a pure Markov process. This includes set correlation, tape, and pre-sim hold alignment — so it's mixing things. The user wanted pure Markov, independent of Monte Carlo. I’ll search for alternative Markov functions like matchOutcomeProbs_Markov.
find other functions related to Markov totals distribution
patterns = ["function matchOutcomeProbs", "function matchTotalDistribution", "function setOutcomeProbs", "function matchOutcomeProbsExact", "function matchOutcome", "Markov"]
We'll search for "function matchTotalDistribution"
m_mtd = re.search(r"function matchTotalDistribution(", text)
m_mtd.start() if m_mtd else None
Searching for game-level Markov functions
It looks like matchTotalDistribution at 430573 probably uses gamesPairDist from matchOutcomeProbs, which suggests the Markov engine may be missing. I think there could be an earlier function specifically for game-level Markov. I’ll search for terms like "gameOutcomeProbs" or "setWinProb" to investigate further.
search for possible pure Markov helpers by looking for "tiebreak" or "dp"
for q in ["dp", "dynamic", "recursion", "memo", "tiebreak", "setWinProb", "gameWinProb", "markov"]:
print(q, len(re.findall(q, text.lower())))
Searching for pure Markov functions
There are 163 "markov" occurrences, but I need to find the ones that are purely Markov-related. I think searching for terms like "DP" (dynamic programming) or "memo" might help narrow it down. I'll also try looking up "function setOutcome" to see if that yields anything useful.
for pat in ["function setOutcome", "function setOutcomeProbs", "function game", "function dp", "function _dp", "function _markov"]:
ms = list(re.finditer(pat.replace("(", r"("), text))
Refining search for Markov-related functions
Since it's tricky to find a direct match, I'll try searching for "function set," though that might be too broad. Instead, I’ll narrow it down with “DP:” or “dynamic programming.” Using regex with "function .*Markov" and "markov" in function names should help me find the relevant pieces.
import re
func_markov = re.findall(r"function\s+([A-Za-z0-9_]markov[A-Za-z0-9_])\s*(", text, flags=re.IGNORECASE)
sorted(set(func_markov))[:50], len(set(func_markov))
m_gm = re.search(r"function getMatchWinProb_Markov(", text)
m_gm.start(), text[m_gm.start():m_gm.start()+200]
gm_sub = text[m_gm.start():m_gm.start()+12000]
gm_sub[:1500]
m_gsw = re.search(r"function getSetWinProb(", text)
m_gsw.start(), text[m_gsw.start():m_gsw.start()+120]
print(text[m_mtd.start():m_mtd.start()+2000])
search for terms inside getSetWinProb block that indicates it computes score distributions
gsw_sub = text[m_gsw.start():m_gsw.start()+30000]
for pat in ["dist", "score", "pmf", "memo", "states", "return", "pSet"]:
print(pat, gsw_sub.lower().find(pat.lower()))
func_setscore = re.findall(r"function\s+([A-Za-z0-9_]Set[A-Za-z0-9_]Score[A-Za-z0-9_])\s(", text)
sorted(set(func_setscore))[:50], len(set(func_setscore))
text.lower().find("setscoredist"), text.lower().find("scoreDist".lower())
pos = m_gsw.start()+2400
print(text[pos:pos+2000])
print(text[m_gsw.start():m_gsw.start()+4000])
print(context(625600, 1400)[:1400])
print(context(625600, 1400)[1400:2800])
mtd_sub = text[m_mtd.start():m_mtd.start()+6000]
mtd_sub.lower().find("median"), mtd_sub[mtd_sub.lower().find("median")-200:mtd_sub.lower().find("median")+200]
print(context(672300, 1200)[:1200])
print(context(672300, 1200)[1200:2400])
pairs = [m.start() for m in re.finditer(r"_pairForSPW(", text)]
len(pairs), pairs[:10]
import re, datetime, math
new_text = text
1) Update patch note (2026-03-01) append bullets
patch_anchor = "* PATCH NOTE (2026-03-01):"
idx = new_text.find(patch_anchor)
idx
insert_after = "* - ORP: added guard to prevent double-adjusting stats objects (avoids ORP being applied multiple times in winner pipeline)."
if insert_after in new_text:
new_text = new_text.replace(insert_after, insert_after + "\n * - Restored 3-engine sovereignty for totals: MC sim runs on baseline holds (no pre-shaved holds); Markov/Dist totals uses a PURE DP engine (no taper/momentum/pre-align); Cluster/Governor measure MC vs Markov disagreement BEFORE any blending.\n * - Disabled Markov-driven post-sim decider alignment correction (prevents circular confirmation loops & double-counted blowout suppression).")
else:
print("anchor line not found")
pattern = r"(function getMatchWinProb_Markov[\s\S]*?return clamp01p \* p \* \(3 - 2 \* p);\n}\n)\n\n/* ===================== Probability Calibration"
m = re.search(pattern, new_text)
bool(m), m.start() if m else None
anchor = "return clamp01(p * p * (3 - 2 * p));"
pos = new_text.find(anchor)
pos
print(new_text[pos-200:pos+200])
markov_pure_block = r"""
/* ===================== PURE MARKOV/DP TOTALS ENGINE (SOVEREIGN) =====================
Purpose: provide an INDEPENDENT cross-check for totals that does NOT share
Monte Carlo path logic, conditional taper, set momentum, or pre-sim hold alignment.
- Inputs: baseline holds (hA,hB), start server, best-of (3/5)
- Model: exact DP on game states for set score distribution (tiebreak at 6-6),
then recursive convolution across sets while tracking next-set first server
via set-length parity.
- Output: matchOutcomeProbs-like object: { outcomes, pair, gamesAdj }
==================================================================================== */
function setScoreDist_MarkovPure(pHoldA, pHoldB, pTB_A, firstServer) {
var hA = clamp01(Number(pHoldA));
var hB = clamp01(Number(pHoldB));
var pTB = Number(pTB_A);
if (!Number.isFinite(pTB)) pTB = 0.5;
pTB = clamp01(pTB);
var fs = String(firstServer || 'A').toUpperCase();
if (fs !== 'A' && fs !== 'B') fs = 'A';
var s0 = (fs === 'A') ? 0 : 1; // 0 => A serves next, 1 => B serves next
var memo = {};
function key(gA, gB, s){ return gA + '' + gB + '' + s; }
function addScaled(dst, src, scale) {
if (!src) return;
for (var k in src) {
if (!src.hasOwnProperty(k)) continue;
dst[k] = (dst[k] || 0) + scale * src[k];
}
}
function rec(gA, gB, s) {
// Terminal: 6 by 2, or 7 games.
if ((gA >= 6 || gB >= 6) && Math.abs(gA - gB) >= 2) {
var outT = {};
outT[gA + '|' + gB] = 1;
return outT;
}
if (gA === 7 || gB === 7) {
var out7 = {};
out7[gA + '|' + gB] = 1;
return out7;
}
// Tiebreak at 6-6
if (gA === 6 && gB === 6) {
var outTB = {};
outTB['7|6'] = pTB;
outTB['6|7'] = 1 - pTB;
return outTB;
}
text
var kk = key(gA, gB, s);
if (memo[kk]) return memo[kk];
// P(A wins next game) depends on server.
var pGameA = (s === 0) ? hA : (1 - hB);
pGameA = clamp01(pGameA);
var nextS = 1 - s;
var d1 = rec(gA + 1, gB, nextS);
var d0 = rec(gA, gB + 1, nextS);
var out = {};
addScaled(out, d1, pGameA);
addScaled(out, d0, 1 - pGameA);
memo[kk] = out;
return out;
}
return rec(0, 0, s0);
}
function setTransitions_MarkovPure(pHoldA, pHoldB, pTB_A, firstServer) {
var fs = String(firstServer || 'A').toUpperCase();
if (fs !== 'A' && fs !== 'B') fs = 'A';
var dist = setScoreDist_MarkovPure(pHoldA, pHoldB, pTB_A, fs);
var out = [];
for (var k in dist) {
if (!dist.hasOwnProperty(k)) continue;
var parts = k.split('|');
var gA = Number(parts[0]), gB = Number(parts[1]);
var p = Number(dist[k]);
if (!Number.isFinite(gA) || !Number.isFinite(gB) || !Number.isFinite(p) || p <= 0) continue;
text
var nGames = gA + gB;
var winner = (gA > gB) ? 'A' : 'B';
// Next set first server is SAME as this set's first server if the set had EVEN games,
// otherwise it flips (serve alternation across an odd-length set).
var nextFS = (nGames % 2 === 0) ? fs : (fs === 'A' ? 'B' : 'A');
out.push({ gA: gA, gB: gB, p: p, winner: winner, nextFS: nextFS });
}
return out;
}
function matchOutcomeProbs_MarkovPure(hA, hB, startServer, bo, pTB_A) {
bo = Number(bo);
if (bo !== 5) bo = 3;
var fs = String(startServer || 'AUTO').toUpperCase();
if (fs !== 'A' && fs !== 'B' && fs !== 'AUTO') fs = 'AUTO';
function computeForFS(fs0) {
var setsToWin = (bo === 5) ? 3 : 2;
var setTransA = setTransitions_MarkovPure(hA, hB, pTB_A, 'A');
var setTransB = setTransitions_MarkovPure(hA, hB, pTB_A, 'B');
text
function getTrans(fsLike) { return (String(fsLike).toUpperCase() === 'B') ? setTransB : setTransA; }
var memo = {};
function stKey(sa, sb, f){ return sa + '_' + sb + '_' + f; }
function addPairScaled(dst, src, scale, addGA, addGB) {
if (!src) return;
for (var k in src) {
if (!src.hasOwnProperty(k)) continue;
var parts = k.split('|');
var ga = Number(parts[0]), gb = Number(parts[1]);
var p = Number(src[k]);
if (!Number.isFinite(ga) || !Number.isFinite(gb) || !Number.isFinite(p) || p <= 0) continue;
var nk = (addGA + ga) + '|' + (addGB + gb);
dst[nk] = (dst[nk] || 0) + scale * p;
}
}
function addScoreScaled(dst, src, scale) {
if (!src) return;
for (var k in src) {
if (!src.hasOwnProperty(k)) continue;
dst[k] = (dst[k] || 0) + scale * src[k];
}
}
function rec(sa, sb, f) {
var kk = stKey(sa, sb, f);
if (memo[kk]) return memo[kk];
// Terminal: match already decided in this state.
if (sa >= setsToWin || sb >= setsToWin) {
var basePair = { '0|0': 1 };
var baseScore = {};
baseScore[sa + '-' + sb] = 1;
var outT = { pair: basePair, score: baseScore };
memo[kk] = outT;
return outT;
}
var trans = getTrans(f);
var outPair = {};
var outScore = {};
for (var i = 0; i < trans.length; i++) {
var t = trans[i];
var nsa = sa + (t.winner === 'A' ? 1 : 0);
var nsb = sb + (t.winner === 'B' ? 1 : 0);
var next = rec(nsa, nsb, t.nextFS);
addPairScaled(outPair, next.pair, t.p, t.gA, t.gB);
addScoreScaled(outScore, next.score, t.p);
}
var out = { pair: outPair, score: outScore };
memo[kk] = out;
return out;
}
return rec(0, 0, fs0);
}
function avgTwo(a, b) {
var out = { pair: {}, score: {} };
function addMap(dst, src, w) {
for (var k in src) {
if (!src.hasOwnProperty(k)) continue;
dst[k] = (dst[k] || 0) + w * src[k];
}
}
addMap(out.pair, a.pair, 0.5);
addMap(out.pair, b.pair, 0.5);
addMap(out.score, a.score, 0.5);
addMap(out.score, b.score, 0.5);
return out;
}
var root = null;
if (fs === 'AUTO') {
var rA = computeForFS('A');
var rB = computeForFS('B');
root = avgTwo(rA, rB);
} else {
root = computeForFS(fs);
}
var pair = root && root.pair ? root.pair : {};
var score = root && root.score ? root.score : {};
// Outcomes (match score probabilities)
var outcomes = {
A_2_0: 0, A_2_1: 0, B_2_0: 0, B_2_1: 0,
A_3_0: 0, A_3_1: 0, A_3_2: 0, B_3_0: 0, B_3_1: 0, B_3_2: 0,
meanGames: 0, medianGames: 0, pDecider: 0
};
if (bo === 5) {
outcomes.A_3_0 = Number(score['3-0'] || 0);
outcomes.A_3_1 = Number(score['3-1'] || 0);
outcomes.A_3_2 = Number(score['3-2'] || 0);
outcomes.B_3_0 = Number(score['0-3'] || 0);
outcomes.B_3_1 = Number(score['1-3'] || 0);
outcomes.B_3_2 = Number(score['2-3'] || 0);
outcomes.pDecider = clamp01(outcomes.A_3_2 + outcomes.B_3_2);
} else {
outcomes.A_2_0 = Number(score['2-0'] || 0);
outcomes.A_2_1 = Number(score['2-1'] || 0);
outcomes.B_2_0 = Number(score['0-2'] || 0);
outcomes.B_2_1 = Number(score['1-2'] || 0);
outcomes.pDecider = clamp01(outcomes.A_2_1 + outcomes.B_2_1);
}
// Mean games from pair distribution
var mean = 0;
var sumP = 0;
for (var k in pair) {
if (!pair.hasOwnProperty(k)) continue;
var parts = k.split('|');
var gA = Number(parts[0]), gB = Number(parts[1]);
var p = Number(pair[k]);
if (!Number.isFinite(gA) || !Number.isFinite(gB) || !Number.isFinite(p) || p <= 0) continue;
mean += (gA + gB) * p;
sumP += p;
}
if (sumP > 0 && Math.abs(sumP - 1) > 1e-6) mean = mean / sumP;
outcomes.meanGames = mean;
// Median games via existing distribution helper
try {
var _td = matchTotalDistribution(pair, bo, 0);
if (_td && Number.isFinite(_td.median)) outcomes.medianGames = Number(_td.median);
} catch(_eMed) {}
return { outcomes: outcomes, pair: pair, gamesAdj: 0 };
}
// =================== END PURE MARKOV/DP TOTALS ENGINE ===================
""".strip("\n")
Insert before calibration comment after getMatchWinProb_Markov
calib_marker = "/* ===================== Probability Calibration"
insert_point = new_text.find(calib_marker, pos)
insert_point
new_text = new_text[:insert_point] + "\n\n" + markov_pure_block + "\n\n" + new_text[insert_point:]
len(new_text)
snippet = "var _effSIM = markovEffectiveHolds(simHA, simHB, _taperSeedA, tourLabel || 'ATP');"
new_text.find(snippet)
pos_effsim = new_text.find(snippet)
print(new_text[pos_effsim:pos_effsim+200])
new_text = new_text.replace(
"var _effSIM = markovEffectiveHolds(simHA, simHB, _taperSeedA, tourLabel || 'ATP');\n simHA = _effSIM.hA_eff;\n simHB = _effSIM.hB_eff;\n",
"// NOTE (2026-03-01): preserve MC sovereignty. Do NOT pre-shave holds with Markov/taper proxies.\n"
)
new_text2 = new_text
Replace EXP_TOTAL_GAMES block
new_text2 = re.sub(r"var\s+_effExp\s*=\smarkovEffectiveHolds[^]);\s*\n\svar\s+MO\s=\smatchOutcomeProbseffExph˙Aeff,\s∗effExph˙Beff,\s∗start,\s∗bo;\s\n",
"var MO = matchOutcomeProbs_MarkovPure(hA, hB, start, bo);\n",
new_text2)
Replace FAIR_TOTAL_LINE block
new_text2 = re.sub(r"var\s+_effFl\s*=\smarkovEffectiveHolds[^]);\s*\n\svar\s+MO\s=\smatchOutcomeProbseffFlh˙Aeff,\s∗effFlh˙Beff,\s∗start,\s∗bo;\s\n",
"var MO = matchOutcomeProbs_MarkovPure(hA, hB, start, bo);\n",
new_text2)
new_text = new_text2
Verify those specific strings absent now for _effExp and _effFl
(" _effExp" in new_text, " _effFl" in new_text)
new_text = re.sub(r"var\s+_effMT\s*=\smarkovEffectiveHolds[^]);\s*\n\svar\s+MO\s=\smatchOutcomeProbseffMTh˙Aeff,\s∗effMTh˙Beff,\s∗start,\s∗bo;\s\n",
"var MO = matchOutcomeProbs_MarkovPure(hA, hB, start, bo);\n",
new_text)
ensure _effMT not present
"_effMT" in new_text
Replace within _pairForSPW
new_text = new_text.replace(
"return matchOutcomeProbs(hA, hB, (serverFirst || 'AUTO').toUpperCase(), bestOf()).pair;",
"return matchOutcomeProbs_MarkovPure(hA, hB, (serverFirst || 'AUTO').toUpperCase(), bestOf()).pair;"
)
occ_after = [m.start() for m in re.finditer(r"markovEffectiveHolds(", new_text)]
len(occ_after), occ_after[:20]
for p in occ_after:
print("\n---", p, "---")
print(new_text[p-200:p+400])
pattern = r"// --- Mismatch decider alignment correction blowoutpacetrim ---[\s\S]?catchp3corrE\s{}\n"
m = re.search(pattern, new_text)
m is not None, (m.start(), m.end()) if m else None
new_text = re.sub(pattern,
"// --- Mismatch decider alignment correction (blowout pace trim) ---\n"
" // DISABLED (2026-03-01): preserves MC/Markov sovereignty and avoids double-counted blowout suppression.\n",
new_text)
Find context around _effMP occurrence again
pos_effMP = new_text.find("var _effMP = markovEffectiveHolds", 0)
pos_effMP
print(new_text[pos_effMP-300:pos_effMP+900])
print(new_text[pos_effMP:pos_effMP+2000])
pattern = r"var _effMP = markovEffectiveHolds[\s\S]?catchp3e\s{}\n"
m = re.search(pattern, new_text[pos_effMP-50:pos_effMP+2200])
bool(m), (m.start(), m.end()) if m else None
new_text = re.sub(
r"var _effMP = markovEffectiveHolds[\s\S]?catchp3e\s{}\n",
"var _MO_TOT_MP = matchOutcomeProbs_MarkovPure(_holdA_tot_mp, _holdB_tot_mp, _startSrvMP, bestOf);\n"
" try {\n"
" var _pDecRawMP = (_MO_TOT_MP && _MO_TOT_MP.outcomes) ? Number(_MO_TOT_MP.outcomes.pDecider) : NaN;\n"
" if (Number.isFinite(_pDecRawMP)) _p3setAdj = clamp01(_pDecRawMP);\n"
" } catch(_p3e) {}\n",
new_text,
count=1
)
pos_effMk = new_text.find("var _effMk = markovEffectiveHolds", 0)
pos_effMk
print(new_text[pos_effMk-400:pos_effMk+2500])
pattern = r"var _effMk = markovEffectiveHolds[\s\S]*?pOverMarkov = normalizeNoPushmkOUp˙Over,mkOUp˙Under;\n"
m = re.search(pattern, new_text[pos_effMk-50:pos_effMk+3000])
bool(m), (m.start(), m.end()) if m else None
replacement = (
"// PURE Markov/DP cross-check (sovereign): constant holds, no taper/momentum/pre-align.\n"
" var mismatch = mismatchIdxFromSeeds({ winProbA: _pFinalForMk, winProbB: 1 - _pFinalForMk, tourLabel: _mkTour }, _holdA_tot, _holdB_tot, _mkTour);\n"
" if (!Number.isFinite(mismatch)) mismatch = 0;\n"
" var hA_eff = clamp01(_holdA_tot);\n"
" var hB_eff = clamp01(_holdB_tot);\n"
"\n"
" _totCtx.holdA = hA_eff;\n"
" _totCtx.holdB = hB_eff;\n"
" _totCtx.pWinA = _pFinalForMk;\n"
" _totCtx.tourLabel = _mkTour;\n"
"\n"
" var _MO_TOT_MK = matchOutcomeProbs_MarkovPure(hA_eff, hB_eff, _startSrvTB, bestOf);\n"
"\n"
" // Markov decider probability (diagnostic; no penalties applied here)\n"
" var _pDecRaw = (_MO_TOT_MK && _MO_TOT_MK.outcomes) ? Number(_MO_TOT_MK.outcomes.pDecider) : NaN;\n"
" pMarkov3Set = Number.isFinite(_pDecRaw) ? clamp01(_pDecRaw) : NaN;\n"
"\n"
" mkMismatch = mismatch;\n"
" mkHoldDrop = 0;\n"
" mkSet3Penalty = 0;\n"
"\n"
" // Keep Markov/Dist totals aligned with any integer correction applied to the MC totals list.\n"
" var _mkGamesAdj = (Number.isFinite(_MO_TOT_MK.gamesAdj) ? _MO_TOT_MK.gamesAdj : 0) + (Number.isFinite(corrInt) ? corrInt : 0);\n"
"\n"
" var _mkTotObjBlend = matchTotalDistribution(_MO_TOT_MK.pair, bestOf, _mkGamesAdj);\n"
" fairLineMarkov = (_mkTotObjBlend && Number.isFinite(_mkTotObjBlend.fairLine)) ? _mkTotObjBlend.fairLine : NaN;\n"
" var _mkHook = analyzeHook(lineTot, _mkTotObjBlend.distribution);\n"
" mkOU = { pOver: _mkHook.over, pUnder: _mkHook.under, pPush: _mkHook.push };\n"
" pOverMarkov = normalizeNoPush(mkOU.pOver, mkOU.pUnder);\n"
)
new_text = re.sub(pattern, replacement, new_text, count=1)
occ_after2 = [m.start() for m in re.finditer(r"markovEffectiveHolds(", new_text)]
len(occ_after2), occ_after2[:10]
find occurrences of "Markov/Dist" near matchOutcomeProbs usage
indices = [m.start() for m in re.finditer(r"Markov/Dist", new_text)]
len(indices), indices[:10]
Find occurrence of "var pOverMCNoPush = pOverGames;"
pos_dir = new_text.find("var pOverMCNoPush = pOverGames;")
pos_dir
print(new_text[pos_dir-200:pos_dir+500])
use_pOverBlend = [m.start() for m in re.finditer(r".pOverBlend\b", new_text)]
len(use_pOverBlend), use_pOverBlend[:20]
old_block = """ // Direction uses a blended no-push probability (MC + Markov) when available.
// Falls back to MC when Markov is missing/invalid.
var pOverMCNoPush = pOverGames;
var pOverEval = (Number.isFinite(pOverBlendEval) ? pOverBlendEval : pOverMCNoPush);
_totCtx.pOverMC = pOverMCNoPush;
_totCtx.pOverBlend = pOverEval;"""
if old_block in new_text:
new_block = """ // Direction uses MC (sovereign). Markov is cross-check only.
var pOverMCNoPush = pOverGames;
var pOverEval = pOverMCNoPush;
_totCtx.pOverMC = pOverMCNoPush;
_totCtx.pOverBlend = pOverEval;
_totCtx.pOverBlendDiag = (Number.isFinite(pOverBlendEval) ? pOverBlendEval : NaN);"""
new_text = new_text.replace(old_block, new_block)
else:
print("old block not found")
new_text = new_text.replace(
"_totCtx.gapP = (Number.isFinite(pOverMarkov) ? Math.abs(pOverEval - pOverMarkov) : NaN);",
"_totCtx.gapP = (Number.isFinite(pOverMarkov) ? Math.abs(pOverMCNoPush - pOverMarkov) : NaN);"
)
new_text = new_text.replace(
" // NOTE: pOverNoPush is expected to be the combined totals probability engine output\n // (MC + Markov blend when available). Raw MC no-push pOver can be passed via ctx.pOverMC.",
" // NOTE (2026-03-01): pOverNoPush is the MC no-push pOver (sovereign direction).\n // Markov/DP no-push pOver can be passed via ctx.pOverMarkov for disagreement governance."
)
new_text.find("Math.abs(pOverEval - pOverMarkov)")
pos_orp = new_text.find("function _orpAdj(raw, baseline, sensitivity)")
pos_orp
print(new_text[pos_orp:pos_orp+800])
print(new_text[pos_orp:pos_orp+1600])
Replace function signature and sampleConf line and add cap logic.
We'll do this via regex on the function body.
1) signature
new_text = new_text.replace("function _orpAdj(raw, baseline, sensitivity) {", "function _orpAdj(raw, baseline, sensitivity, capMove) {")
2) sampleConf formula
new_text = new_text.replace("var sampleConf = Math.min(1.0, Math.max(0.35, sg / 40));",
"var sampleConf = Math.min(1.0, Math.max(0.15, sg / 60));")
3) insert cap logic before return adj; locate "return adj;" line within _orpAdj
cap_insertion = """ // Cap ORP movement per-stat (prevents ORP from erasing true skill signals).
var _cap = Number(capMove);
if (Number.isFinite(_cap) && _cap > 0) {
var _d = adj - raw;
if (_d > _cap) adj = raw + _cap;
else if (_d < -_cap) adj = raw - _cap;
adj = clamp01(adj);
}
"""
Insert right before "return adj;" in _orpAdj (first occurrence after the function start position)
We'll use regex to replace " return adj;" with cap insertion + return.
new_text = new_text.replace(" return adj;\n", cap_insertion + " return adj;\n", 1)
verify cap insertion exists near _orpAdj
pos_orp2 = new_text.find("Cap ORP movement per-stat")
pos_orp2, new_text[pos_orp2-200:pos_orp2+300]
define mapping for each call; use replace multiple times
repls = {
"_orpAdj(adjusted.sgWinRate, baseHold, 0.12)": "_orpAdj(adjusted.sgWinRate, baseHold, 0.12, 0.03)",
"_orpAdj(adjusted.holdRate, baseHold, 0.12)": "_orpAdj(adjusted.holdRate, baseHold, 0.12, 0.03)",
"_orpAdj(adjusted.hold, baseHold, 0.12)": "_orpAdj(adjusted.hold, baseHold, 0.12, 0.03)",
"_orpAdj(adjusted.spw, baseSPW, 0.09)": "_orpAdj(adjusted.spw, baseSPW, 0.09, 0.02)",
"_orpAdj(adjusted.rpWinRate, baseRPW, 0.10)": "_orpAdj(adjusted.rpWinRate, baseRPW, 0.10, 0.02)",
"_orpAdj(adjusted.rpw, baseRPW, 0.10)": "_orpAdj(adjusted.rpw, baseRPW, 0.10, 0.02)",
"_orpAdj(adjusted.srv1Rate, baseSrv1, 0.05)": "_orpAdj(adjusted.srv1Rate, baseSrv1, 0.05, 0.02)",
"_orpAdj(adjusted.srv2Rate, baseSrv2, 0.06)": "_orpAdj(adjusted.srv2Rate, baseSrv2, 0.06, 0.02)",
"_orpAdj(adjusted.bpConvRate, baseBrk, 0.09)": "_orpAdj(adjusted.bpConvRate, baseBrk, 0.09, 0.02)",
"_orpAdj(adjusted.retGameWinRate, baseBrk, 0.12)": "_orpAdj(adjusted.retGameWinRate, baseBrk, 0.12, 0.02)",
}
for k,v in repls.items():
new_text = new_text.replace(k, v)
verify no old patterns remain
missing = [k for k in repls if k in new_text]
missing[:5], len(missing)
new_text.find("_effSIM"), len(re.findall(r"_effSIM", new_text))
for varname in ["_effMP", "_effP3", "_effMk"]:
print(varname, new_text.find(varname))
out_path = Path("/mnt/data/Tennislocks_V17.4_Cluster_Governor_v17.4.6_SOVEREIGN.txt")
out_path.write_text(new_text, encoding="utf-8")
str(out_path), out_path.stat().st_size
bash -lc node -e "const fs=require('fs'); const code=fs.readFileSync('/mnt/data/Tennislocks_V17.4_Cluster_Governor_v17.4.6_SOVEREIGN.txt','utf8'); try{ new Function(code); console.log('SYNTAX_OK'); } catch(e){ console.error('SYNTAX_ERROR'); console.error(e && e.stack || e); process.exit(1);}"
bash -lc node -e "const fs=require('fs'); const vm=require('vm'); const code=fs.readFileSync('/mnt/data/Tennislocks_V17.4_Cluster_Governor_v17.4.6_SOVEREIGN.txt','utf8'); const sandbox={console}; try{ vm.runInNewContext(code, sandbox, {timeout: 2000}); console.log('TOPLEVEL_OK'); } catch(e){ console.error('TOPLEVEL_ERROR'); console.error(e && e.stack || e); process.exit(1);}"
len(re.findall(r"matchOutcomeProbs_MarkovPure", new_text)), new_text.find("matchOutcomeProbs_MarkovPure")
Thought for 19m 58s