Calculation catalog › Monte Carlo
Monte carlo depletion probability by year
Per depletion year, the share of all paths first depleting that year and the cumulative share depleted by that year.
Field: Depletion probability by year Unit: probability Basis: no dollar basis Computed by: Engine Status: Complete chain
Appears in: monte-carlo-page ("Depletion probability by year" chart: "First depletion" bars and cumulative line, aria-label cumulative percent); scenarios-page (Risk 'Cumulative depletion probability by year' table (ScenarioRiskComparison.depletionProbabilityByYear[].cumulativeProbability, read at each year from MonteCarloSummary.depletionProbabilityByYear))
Calculation records
-
Depletion probability and cumulative probability by year Formula record, justified by derivation
Purpose: The chance the plan first runs out in each depletion year, taken over every path, with the running total.
Statement: One row per depletion year, in depletionYearCounts order: probability = count / pathCount, with the successful paths kept in the denominator, and cumulativeProbability = the running sum of those probabilities through that row. For six paths depleting in 2031 (1), 2033 (2) and 2035 (1) alongside two successes, the rows are 1/6, 1/3 and 1/6 with cumulative 1/6, 1/2 and 2/3. Units: probability in [0, 1]. Rounding: none; absolute tolerance 1e-9, since each figure is a ratio of small integers accumulated in floating point.
Formula:
probability(y) = count(y) / N; cumulativeProbability(y_k) = sum_{j <= k} probability(y_j), N = total paths-
N: Total paths, successes included (unit: paths) (domain: worksheet 6) -
count(y): Paths first depleting in year y (unit: paths) (domain: worksheet 1, 2, 1) -
probability(y): Share of all paths first depleting in year y (unit: 1) (domain: [0, 1])
Timing: once per aggregation, after the depletion-year rows are ordered
Rounding: none
Limits:
- The denominator is every path, successes included; dividing by the four failures alone would give 1/4, 1/2, 1/4 with a final cumulative of 1
- Successes never form a row of their own; they act only on the denominator
- cumulativeProbability is accumulated left to right in floating point, so the last row equals the failure rate only up to that accumulation
- The evidence supplies depletionYear only; every other MonteCarloPath field is at the test’s neutral (zero balances and shortfalls, floor and target met, attainment 1, no goal or guardrail activity, no bequest target)
Provenance: derived by
codex, implemented byclaude.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: monte-carlo-depletion-probability-by-year.md Mutation receipt: monte-carlo-depletion-probability-by-year.mutation.md
Evidence tests: run.evidence.test.ts
Implemented in: run.ts
Is the number of: monte-carlo-depletion-probability-by-year
-
Records that feed this number
-
Embedded annual stock, bond, and inflation series, 1928–2023 Data record, justified by dataset
Purpose: The offline 96-year Damodaran/Shiller snapshot every historical bootstrap and blend reads.
Statement: Ninety-six calendar-year rows, 1928 through 2023 inclusive, each storing S&P 500 total return, 10-year US Treasury total return, and CPI-U calendar-year inflation, all in percent, transcribed to one decimal. The dataset fact this record pins is the embedded table: 96 rows, those endpoints, column sums 1118.9 / 466.6 / 298.8 percentage points, and the sample rows 1928 43.8/0.8/−1.2, 1929 −8.3/4.2/0.6, 2023 26.1/3.9/3.4. Values are approximate at about 0.1–0.5 percentage point. Rounding: one decimal as stored.
Limits:
- Each value is approximate at about 0.1–0.5 percentage point; bootstrap sampling uses the joint distribution and sequencing, not basis-point precision
- Refresh cadence is annual with the published parameters
- The full table is not reproduced in the worksheet; the evidence pins row count, endpoints, column sums, and the three sample rows the worksheet names
Dataset: Aswath Damodaran / NYU Stern, “Historical Returns on Stocks, Bonds and Bills,” derived from Shiller annual series: S&P 500 total return, 10-year Treasury total return, and calendar-year CPI inflation , as of 2023-12-31 , retrieved 2026-09-14
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: historical-market-series.md Mutation receipt: historical-market-series.mutation.md
Evidence tests: historicalReturns.evidence.test.ts
Implemented in: historicalReturns.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
Arithmetic mean of blended historical portfolio returns Formula record, justified by derivation
Purpose: The sample mean every centered historical bootstrap subtracts so the average additive shock is zero.
Statement: For equity weight w percent, mean = (1/96) sum_t [w/100 · stocks_t + (1 − w/100) · bonds_t] over the 96 embedded years. Equivalently w/100 · mean(stocks) + (1 − w/100) · mean(bonds). Arithmetic, not geometric, so the average additive shock is zero. For w = 60 the column sums 1118.9 and 466.6 give 8.93729166666667%. Units: percent per year, nominal. Rounding: none.
Formula:
mean(w) = (1/N) sum_t [ (w/100) stocks_t + (1 − w/100) bonds_t ], N = 96-
w: Equity weight (unit: percent) (domain: typically 0–100) -
stocks_t, bonds_t: Embedded year-t stock and bond total returns (unit: percent/year) (domain: finite) -
mean(w): Arithmetic mean blended return (unit: percent/year) (domain: finite)
Timing: once per configured equity weight; subtracted from every sampled year of a centered bootstrap
Rounding: none
Limits:
- Arithmetic mean is required to center additive shocks; a geometric mean would not make the average shock zero
- The mean is taken over the embedded 96-year window only; it is not a forecast of the plan expected return
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: historical-portfolio-mean.md Mutation receipt: historical-portfolio-mean.mutation.md
Evidence tests: historicalReturns.evidence.test.ts
Implemented in: historicalReturns.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
One-year two-asset blended nominal return Formula record, justified by derivation
Purpose: The dollar-weighted stock/bond mix of one historical year, before centering.
Statement: For a year with stock return s percent and bond return b percent and equity weight w percent, the blended nominal return is (w/100)·s + (1 − w/100)·b, with no rounding. For 1928 (43.8, 0.8) at w = 60 this is 26.6%. Units: percent per year, nominal. Domain: finite series values; w is used as a fraction and is not clamped.
Formula:
R = (w/100) s + (1 − w/100) b-
s: S&P 500 total return for the year (unit: percent/year) (domain: finite) -
b: 10-year Treasury total return for the year (unit: percent/year) (domain: finite) -
w: Equity weight (unit: percent) (domain: typically 0–100; not clamped) -
R: Blended nominal portfolio return (unit: percent/year) (domain: finite)
Timing: one calendar year; one blend per sampled historical row
Rounding: none
Limits:
- A two-asset stock/bond mix; cash, international, and class-level sleeves are not in this blend
- The weight is not clamped to [0, 100]; a value outside that range is used as passed
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: historical-portfolio-return-blend.md Mutation receipt: historical-portfolio-return-blend.mutation.md
Evidence tests: historicalReturns.evidence.test.ts
Implemented in: historicalReturns.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
AR(1) mean-reverting return shock Model record, justified by derivation
Purpose: A serially correlated, mean-zero additive return shock for one Monte Carlo path.
Statement: Let phi be the configured persistence (default 0.25; a number strictly between −1 and 1, where the process is stationary, and anything else is refused with a message) and sigma = returnVolPct/100. Starting from prev = 0, each year draws an innovation eps ~ N(0, 1) and sets shock = phi · prev + sigma · eps, then prev = shock; the published return shock is 100 · shock percentage points. With phi = 0.2, sigma = 0.10 and a first-year eps that produces a 10-point shock, the next two zero-innovation years are 2 then 0.4 points. Units: percentage points. Rounding: none.
Formula:
x_0 = 0; x_t = phi x_{t-1} + sigma eps_t; returnShockPct_t = 100 x_t-
phi: AR(1) persistence (unit: 1) (domain: strictly between −1 and 1, refused otherwise; worksheet case 0.2) -
sigma: Innovation scale, returnVolPct/100 (unit: 1) (domain: >= 0) -
eps_t: Standard-normal innovation (unit: 1) (domain: finite) -
returnShockPct_t: Additive return shock (unit: percentage points) (domain: finite)
Timing: annual; prev starts at 0 on every path
Rounding: none
Limits:
- A dynamics assumption, not evidence of a true return process
- The path always starts from prev = 0; a prior shock of 10 is realized as year 1 of the path, and the worksheet's "next two shocks" are years 2 and 3 with zero innovations
- The phi default comment was corrected to the code's 0.25 on 2026-09-18 (D-STUDENT-T-MIXTURE notes the related decision). The worksheet case passes 0.2 explicitly
- A phi of 1 or more in size, where the process is not stationary, is refused with a message; phi used to be clamped to [−0.9, 0.95] without a word, and values inside that range run exactly as before
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite returnVolPct or inflationVolPct is refused with a message
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-ar1-shock.md Mutation receipt: market-model-ar1-shock.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
CAPE-conditioned shift of a mean-preserving lognormal shock Model record, justified by derivation
Purpose: Lowers the path mean when starting CAPE is above 20, on top of a lognormal shock.
Statement: The lognormal base shock is 100 (exp(sigma Z − sigma^2/2) − 1). The CAPE adjustment is adj = clamp(−(startingCape − 20) · sensitivity, −4, 2) percentage points, and the published shock is the base plus adj. For startingCape = 25, sensitivity = 0.15 and sigma = 0, adj = −0.75 and the shock is −0.75 points. Units: percentage points. Rounding: none.
Formula:
adj = clamp(−(CAPE − 20) · s, −4, 2); shock = 100 (exp(sigma Z − sigma^2/2) − 1) + adj-
CAPE: Starting cyclically adjusted P/E (unit: 1) (domain: positive; worksheet 25) -
s: Sensitivity, percentage points per CAPE point above 20 (unit: percentage points) (domain: worksheet 0.15) -
sigma: Lognormal volatility as a decimal (unit: 1) (domain: >= 0; worksheet 0) -
adj: Additive mean shift (unit: percentage points) (domain: [−4, 2])
Timing: annual; the CAPE adjustment is constant across years of the path
Rounding: none
Limits:
- A modeling hypothesis, not proof that CAPE causes the stated future-return change
- The adjustment is capped to [−4, 2] percentage points on purpose: the cap bounds a derived value, not an input the caller set; the worksheet case −0.75 does not bind
- The projection still adds the plan expected return separately; this record is the shock only
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite returnVolPct or inflationVolPct is refused with a message
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-cape-conditioned.md Mutation receipt: market-model-cape-conditioned.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Empirical historical shock, centered or raw Model record, justified by derivation
Purpose: Samples blended historical returns as shocks, optionally subtracting the historical mean.
Statement: Each year samples a HISTORICAL_YEARS row: the first nextInt(n) sets the cursor, then a uniform coin of 0.5 redraws iid when next() < 0.5 and otherwise keeps the cursor. At equity weight w, centered mode (the default) publishes blend(row, w) − meanPortfolioReturnPct(w); raw mode (centered === false) publishes the blend itself. Inflation is the row value as-is. For w = 60, nextInt(96) = 0 and next() = 0.6, year 1 is 1928: blend 26.599999999999998%, dataset mean 8.937291666666669%, centered shock 17.662708333333327, raw shock 26.599999999999998, inflation −1.2%. Units: percentage points and percent/year. Rounding: none; absolute tolerance 1e-12 on the non-integer shocks.
Formula:
shock_centered = R(row, w) − mean(w); shock_raw = R(row, w); inflation = row.inflationPct-
row: Sampled HISTORICAL_YEARS entry (unit: year) (domain: 1928–2023; worksheet 1928 via nextInt 0) -
w: Equity weight (unit: percent) (domain: worksheet 60) -
mean(w): Dataset blended mean at w, meanPortfolioReturnPct(w) (unit: percent/year) (domain: worksheet 8.937291666666669) -
R(row, w): Blended historical return of the sampled row (unit: percent/year) (domain: worksheet 26.599999999999998)
Timing: annual; one sampled row per path year
Rounding: none
Limits:
- Centered mode studies dispersion about the historical mean; raw mode preserves the empirical level but can double-count because the projection adds expected return separately
- Neither mode predicts future returns
- The sampler mixes sequence-step and iid (a 0.5 coin) rather than drawing one iid index per year
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-empirical-history.md Mutation receipt: market-model-empirical-history.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
GARCH(1,1) variance recursion with variance targeting Model record, justified by derivation
Purpose: A clustered-volatility additive return shock whose long-run standard deviation equals returnVolPct.
Statement: With sigmaBar = returnVolPct/100 (default 12), alpha (default 0.1) and beta (default 0.85), omega = sigmaBar^2 · (1 − alpha − beta), so the long-run variance omega / (1 − alpha − beta) is sigmaBar^2 (Bollerslev 1986, with variance targeting as the Federal Reserve's stress-test model documentation of October 2025 states it, after Engle and Mezrich 1996; the defaults alpha and beta are the model's choices). The path starts at v_1 = sigmaBar^2. Each year t draws Z1_t = nextNormal, sets the innovation e_t = sqrt(v_t) · Z1_t and publishes returnShockPct = 100 · e_t; then Z2_t = nextNormal and inflation = inflationMeanPct + inflationVolPct · (rho · Z1_t + sqrt(1 − rho^2) · Z2_t); then v_{t+1} = omega + alpha · e_t^2 + beta · v_t, which feeds back the innovation itself. Because E[e_t^2 | past] = v_t and v_1 = sigmaBar^2, E[v_t] = sigmaBar^2 in every year, so every year's shock has mean 0 and unconditional standard deviation returnVolPct points. A negative or non-finite returnVolPct, a negative or non-finite alpha or beta, alpha + beta of 1 or more, and the retired keys omega (now derived) and returnVolScalePct (now returnVolPct) are refused with a message. For the defaults, zero inflation inputs and Z1 = 1, 0.5, −2, 0 (Z2 = 0), omega = 0.00072 and v = 0.0144, 0.0144, 0.01332, 0.01737, so the shocks are 12, 6, −23.082460874005616 and 0 points and inflation is 0. Units: percentage points. Rounding: none. Absolute tolerance 1e-12.
Formula:
v_1 = sigmaBar^2; e_t = sqrt(v_t) · Z1_t; shock_t = 100 · e_t; v_{t+1} = sigmaBar^2 (1 − alpha − beta) + alpha e_t^2 + beta v_t-
returnVolPct: Long-run standard deviation of the shock; sigmaBar = returnVolPct/100 (unit: percentage points) (domain: finite and >= 0, refused otherwise; worksheet 12) -
alpha, beta: Weights on the prior year's squared innovation and on its variance (unit: 1) (domain: finite, >= 0 and alpha + beta < 1, refused otherwise; worksheet 0.1 and 0.85) -
v_t: Conditional variance of year t (internal, not published) (unit: fraction squared) (domain: >= omega; worksheet 0.0144, 0.0144, 0.01332, 0.01737) -
Z1_t, Z2_t: The year's two standard-normal draws (unit: 1) (domain: worksheet Z1 = 1, 0.5, −2, 0 with Z2 = 0) -
shock_t: Published additive return shock (unit: percentage points) (domain: worksheet 12, 6, −23.082460874005616, 0)
Timing: annual; v persists along the path from v_1 = sigmaBar^2; draws are Z1, Z2, then the class normals
Rounding: none
Limits:
- alpha 0.1 and beta 0.85 are illustrative values, not fitted to annual return data; no source fixes annual GARCH parameters
- Innovations are Gaussian: the fat tails come only from the changing variance (a stationary kurtosis of 3.77 at the defaults)
- Allocated accounts' class shocks share one common variance with the market: each class keeps its configured long-run volatility and correlations, and each year every class is scaled by the same sqrt(v_t)/sigmaBar, so all classes cluster together rather than each following a variance of its own (at returnVolPct 0 the scale is 1)
- The variance v_t is internal and not published; only the shock, inflation and class series are
- An additive shock can take a year's return below −100 percent (the return is the plan's expected return plus the shock), and the ledger floors balances at zero (annualPostSolveAccountGrowth.ts); after a large shock the next year's variance, and with it that chance, is higher
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite inflationVolPct is refused with a message
Provenance: derived by
claude, implemented byclaude-subagent.Independent review:
codexGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-garch-variance.md Mutation receipt: market-model-garch-variance.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Additive Gaussian return shock Model record, justified by derivation
Purpose: A symmetric, mean-zero normal return shock scaled to the configured volatility.
Statement: Each year draws Z ~ N(0, 1) and emits returnShockPct = returnVolPct · Z (equivalently 100 · (returnVolPct/100) · Z). For volatility 12 and Z = −2 the shock is −24 percentage points. Inflation is normal and correlated via a Gaussian copula. Units: percentage points. Rounding: none. Exact for these inputs.
Formula:
returnShockPct = sigma_pct · Z-
sigma_pct: Annual return volatility (unit: percentage points) (domain: >= 0; worksheet 12) -
Z: Standard-normal draw (unit: 1) (domain: finite; worksheet −2) -
returnShockPct: Additive return shock (unit: percentage points) (domain: unbounded; can imply returns below −100%)
Timing: annual; iid given the draws
Rounding: none
Limits:
- The unbounded lower tail can imply returns below −100%, an explicit limitation of additive normal shocks
- This is simulated dispersion, not a forecast
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite returnVolPct or inflationVolPct is refused with a message
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-gaussian-draw.md Mutation receipt: market-model-gaussian-draw.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Centered historical bootstrap shock Model record, justified by derivation
Purpose: A mean-zero shock from a sampled historical stock/bond/inflation year at the configured equity weight.
Statement: A year is sampled iid, in a fixed-length wrapping block, or as a wrapping sequence. The return shock is portfolioReturnPct(row, w) − meanPortfolioReturnPct(w); inflation is the row's inflation. With mode iid, w = 60, and nextInt(96) = 0, path year 1 is the 1928 row: blend 26.599999999999998%, dataset mean 8.937291666666669%, centered shock 17.662708333333327 percentage points, inflation −1.2%. Units: percentage points and percent/year. Rounding: none; absolute tolerance 1e-12 on the shock.
Formula:
shock = R(row, w) − mean(w); inflation = row.inflationPct-
row: Sampled HISTORICAL_YEARS entry (unit: year) (domain: 1928–2023; worksheet 1928 via nextInt 0) -
w: Equity weight (unit: percent) (domain: worksheet 60) -
mean(w): Dataset blended mean at w, meanPortfolioReturnPct(w) (unit: percent/year) (domain: worksheet 8.937291666666669) -
shock: Centered return shock (unit: percentage points) (domain: worksheet 17.662708333333327)
Timing: annual; iid / block / sequence chooses the row
Rounding: none
Limits:
- Subtracting the sample-series mean makes the empirical shock mean zero; it does not claim history repeats or that a 60/40 blend fits every portfolio
- blockLengthYears (default 5) must be a whole number of at least 1, in every mode; anything else is refused with a message rather than rounded
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-historical-centered-bootstrap.md Mutation receipt: market-model-historical-centered-bootstrap.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Bernoulli high-inflation regime mix Model record, justified by derivation
Purpose: A two-regime inflation mean (normal vs high) with a lognormal return shock.
Statement: Each year a uniform U is compared with the high-inflation probability (from the low regime) or 0.7 (from the high regime) and the regime toggles on a hit. Inflation is the selected mean plus a 1.5-point copula term. For high-inflation probability 0.20, U = 0.10, high/base means 8/3 and zero innovations, the first year starts in the low regime, 0.10 < 0.20 toggles to high, and inflation is 8%. Units: percent/year. Rounding: none. Exact in this degenerate case.
Formula:
start low; if U < p_high then high; inflation = (high ? mu_high : mu_base) + 1.5 · copula(Z)-
p_high: Probability of switching into the high regime from low (unit: 1) (domain: a probability from 0 to 1, refused otherwise; worksheet 0.20) -
U: Regime uniform draw (unit: 1) (domain: [0, 1); worksheet 0.10) -
mu_high, mu_base: High and base inflation means (unit: percent/year) (domain: worksheet 8 and 3)
Timing: annual; the regime bit persists along the path
Rounding: none
Limits:
- A stress model, not a forecast of regime incidence
- Inflation volatility is hardcoded at 1.5 percentage points; zero-innovation inflation requires Z = 0, not a configured vol of 0
- A high-inflation probability outside [0, 1] is refused with a message; it used to be clamped to [0.01, 0.3] without a word, and values inside that range run exactly as before
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite returnVolPct is refused with a message
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-inflation-regime.md Mutation receipt: market-model-inflation-regime.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Mean-preserving lognormal return shock Model record, justified by derivation
Purpose: A multiplicative shock whose gross multiplier has mean one, plus correlated normal inflation.
Statement: Each year draws Z ~ N(0, 1) and emits returnShockPct = 100 (exp(sigma Z − sigma^2/2) − 1) with sigma = returnVolPct/100, so E[exp(sigma Z − sigma^2/2)] = 1. Inflation is inflationMeanPct + inflationVolPct · copula(Z). For return volatility 0 and inflation mean/vol 3/0 every year has return shock 0 and inflation 3%, regardless of Z. Units: percentage points and percent/year. Rounding: none. Exact apart from array representation.
Formula:
returnShockPct = 100 (exp(sigma Z − sigma^2/2) − 1); inflation = mu_i + vol_i · copula(Z)-
sigma: Return volatility as a decimal, returnVolPct/100 (unit: 1) (domain: >= 0; worksheet 0) -
Z: Standard-normal draw (unit: 1) (domain: finite; arbitrary when sigma = 0) -
mu_i, vol_i: Inflation mean and volatility (unit: percent/year) (domain: worksheet 3 / 0)
Timing: annual; iid given the draws
Rounding: none
Limits:
- Defines simulated dispersion, not a forecast or a guarantee that returns are lognormal
- The plan expected return is added by the projection; putting it inside this model would double-count
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite returnVolPct or inflationVolPct is refused with a message
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-lognormal-draw.md Mutation receipt: market-model-lognormal-draw.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Two-state bull/bear Markov return shock Model record, justified by derivation
Purpose: Persistent bull and bear mean deviations with a per-year switch probability.
Statement: The path starts bull if U_start > 0.5. Each year, if U_switch < p_switch the state flips. The shock is 100 · (mu_state + sigma_state · Z) percentage points, with mu_bull / mu_bear the configured deviations (default +4 / −4 percent as decimals 0.04 / −0.04). For bull/bear +4/−4, zero volatilities, current state bull, switch draw 0.9 and p_switch = 0.05, the state remains bull and the shock is +4. Units: percentage points. Rounding: none. Exact for the degenerate volatility case.
Formula:
stay if U >= p; shock = 100 (mu_state + sigma_state Z)-
mu_bull, mu_bear: State mean deviations as percents, divided by 100 in the recursion (unit: percent) (domain: worksheet +4 / −4) -
p: Annual switch probability (unit: 1) (domain: a probability from 0 to 1, refused otherwise; worksheet 0.05) -
U: Switch uniform draw (unit: 1) (domain: [0, 1); worksheet 0.9)
Timing: annual; the state bit persists along the path
Rounding: none
Limits:
- A scenario model, not evidence markets have two regimes
- Symmetric +a/−a deviations are near zero only under equal long-run state mass
- The start state is a coin flip (U > 0.5), so the worksheet's "current state bull" is realized by scripting that first draw
- A switch probability outside [0, 1] is refused with a message; it used to be clamped to [0.001, 0.5] without a word, and values inside that range run exactly as before
- A negative or non-finite bullVolPct, bearVolPct or inflationVolPct is refused with a message
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-regime-switch.md Mutation receipt: market-model-regime-switch.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Reversed-history window replay Model record, justified by derivation
Purpose: Replays a stochastically chosen historical window in reverse chronological order.
Statement: The window length L = windowLengthYears (default 10) must be a whole number of years from 5 to the historical row count n = 96; any other value is refused with a message and never clamped. The path draws start = nextInt(n − L + 1) once, then path year i replays row start + L − 1 − (i mod L). Each shock is the row's blend at equityWeightPct minus the dataset mean at that weight; inflation is the row value as-is. For windowLengthYears 5, w = 60, nextInt(92) = 72, and three path years, the replayed years are [2004, 2003, 2002], shocks [−0.7172916666666698, 8.26270833333333, −16.097291666666667], inflation [3.3, 1.9, 2.4]. Units: percentage points and percent/year. Rounding: none; absolute tolerance 1e-12 on the shocks.
Formula:
L = windowLengthYears (whole, 5 ≤ L ≤ n); idx_i = start + L − 1 − (i mod L); shock_i = R(row_idx, w) − mean(w)-
L: Window length, windowLengthYears (unit: years) (domain: whole number from 5 to n = 96, refused otherwise; worksheet 5) -
start: Drawn start index, nextInt(n − L + 1) (unit: 1) (domain: 0 to n − L; worksheet 72) -
w: Equity weight (unit: percent) (domain: worksheet 60)
Timing: annual; the reversed window repeats for longer paths
Rounding: none
Limits:
- A stress transformation that preserves marginal observations while changing sequence risk, not a historical claim
- A windowLengthYears that is not a whole number from 5 to 96 is refused with a message rather than run as a different window; the planner always sends 10
- Paths longer than L repeat the same reversed window
Provenance: derived by
claude, implemented byclaude-subagent.Independent review:
codexGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-reversed-history.md Mutation receipt: market-model-reversed-history.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Stationary (geometric-block) historical bootstrap Model record, justified by derivation
Purpose: Samples historical years in wrapping contiguous blocks whose length is geometric with mean L.
Statement: With L = meanBlockLength ?? 5 (a finite number of at least 2; anything else is refused with a message), each historical block draws one length remaining = floor(−ln(1 − U) · L) || 1 after cursor = nextInt(n) (U = next()). The path publishes that many contiguous rows with wrap, then draws a new start row and length only when remaining reaches 0. Draw order per block is nextInt then next. A per-year continuation coin with probability 1/L is a different law. For L = 5, equityWeightPct = 60, nextInt 72, U = 0.50, then at restart nextInt 0, U = 0.90, a five-year path replays 2000, 2001, 2002, 1928, 1929: inflation 3.4, 1.6, 2.4, −1.2, 0.6 and shocks −7.657291666666668, −13.837291666666669, −16.097291666666667, 17.662708333333327, −12.23729166666667. Units: percentage points and percent/year. Rounding: block length is floored and a zero floor becomes 1. Absolute tolerance 1e-12 on the shocks; inflation exact.
Formula:
at each block start: remaining = floor(−ln(1 − U) · L) || 1; each year publishes the row at cursor, then cursor = (cursor + 1) mod n and remaining decrements; when remaining reaches 0 a new cursor = nextInt(n) and a new length are drawn-
L: Mean block length (unit: years) (domain: finite and >= 2, refused otherwise; worksheet 5) -
U: Uniform length draw, once per block (unit: 1) (domain: [0, 1); worksheet 0.50 then 0.90) -
remaining: Years left in the current block (unit: years) (domain: >= 1 after the || 1 clamp; worksheet 3 then 11)
Timing: annual; a new block is drawn when remaining hits 0
Rounding: block length is floored; a zero floor becomes 1
Limits:
- Preserves observations while randomizing dependence lengths; it does not establish that historical dependence recurs
- One block-length draw per block: remaining = floor(−ln(1 − U) · L) || 1. A per-year continuation coin with p = 1/L is a different law even when its mean is near L
- A meanBlockLength below 2, or not finite, is refused with a message rather than raised to 2
- Draw order per block is nextInt(n) then next(); a new pair is drawn only when remaining reaches 0
- The || 1 is kept on purpose: it rejects an empty block, since at U = 0.01 and L = 5, floor(−ln(0.99) · 5) = 0 would otherwise be length 0
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-stationary-bootstrap.md Mutation receipt: market-model-stationary-bootstrap.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Student-t return shock scaled to the configured volatility Model record, justified by derivation
Purpose: A fat-tailed additive return shock whose standard deviation equals returnVolPct.
Statement: For degrees of freedom df > 2 (default 5) and sigma = returnVolPct/100, each year draws Z ~ N(0, 1) with nextNormal; then V ~ chi-square(df) as 2G with G ~ Gamma(df/2, 1) by the method of Marsaglia and Tsang (2000, ACM TOMS 26(3):363-372), which uses uniforms only (its normal is built by Box-Muller from its own two uniforms, cosine branch); then m = sqrt((df − 2)/V) and t = m · Z, and the published shock is returnShockPct = sigma · t · 100; then z2 = nextNormal and inflation = inflationMeanPct + inflationVolPct · (rho · t + sqrt(1 − rho^2) · z2). For an ideal chi-square V, E[1/V] = 1/(df − 2) and E[t^2] = 1, so the shock has mean 0 and standard deviation returnVolPct points, and Corr(shock, inflation) = rho when returnVolPct and inflationVolPct are both positive (undefined when either is 0); the implemented V, whose private normal is capped (a limit), makes these approximate rather than exact. A df of 2 or less, or not finite, is refused with a message. For df = 5, returnVolPct = 12, Z = 1 and uniforms 0.5, 0, 0.5: the method's normal is x = sqrt(2 ln 2) = 1.1774100225154747, d = 13/6, c = 1/sqrt(19.5), v = (1 + c x)^3 = 2.0321239789552172, and 0.5 is below the squeeze bound 1 − 0.0331 x^4 = 0.9363880209572302, so V = 2 d v = 8.805870575472607, m = 0.5836795510996967 and the shock is 7.00415461319636 points; inflation inputs are zero, so inflation is 0. Units: percentage points. Rounding: none. Absolute tolerance 1e-12.
Formula:
t = Z · sqrt((df − 2)/V), V ~ chi-square(df) by Marsaglia and Tsang; returnShockPct = returnVolPct · t-
df: Degrees of freedom of the t variate (unit: 1) (domain: finite and > 2, refused otherwise; worksheet 5) -
Z: The year's first standard-normal draw (unit: 1) (domain: worksheet 1) -
V: Chi-square(df) draw, from uniforms only (unit: 1) (domain: > 0; worksheet 8.805870575472607) -
m: Unit-variance mixing scale sqrt((df − 2)/V), also shared by the class shocks (unit: 1) (domain: > 0; worksheet 0.5836795510996967) -
returnVolPct: Standard deviation of the shock (unit: percentage points) (domain: worksheet 12) -
returnShockPct: Additive return shock (unit: percentage points) (domain: worksheet 7.00415461319636)
Timing: annual; draws are nextNormal, uniforms for V (2 per attempt rejected at s <= 0, else 3), nextNormal
Rounding: none
Limits:
- A tail model, not a claim that future returns follow a t distribution; the planner always uses df 5, and no source fixes df
- A df of 2 or less is refused, because the variance is infinite there; for df of 4 or less the fourth moment is infinite
- Allocated accounts' class shocks share V with the market, so the class vector is approximately a multivariate t (exactly one for an ideal chi-square V): large moves in magnitude co-occur across classes (tail dependence) even where the class correlation is low, while their direction follows the correlation; cash gets t tails too
- Inflation reads t rather than Z, so it inherits a little of the tail at the default correlation of −0.2; this keeps the configured correlation, for positive return and inflation volatilities and up to the capped normal inside V; with either volatility 0 the correlation is undefined
- An additive shock can take a year's return below −100 percent (the return is the plan's expected return plus the shock), and the ledger floors balances at zero (annualPostSolveAccountGrowth.ts). At df 5 the chance per year of a shock at or below −105 points is 4.8e-5 at 12 percent volatility, 5.3e-4 at 20 percent and 1.4e-3 at 25 percent; the earlier mixture was about 2.3e-3 at 25 percent
- The normal inside the chi-square draw floors its first uniform at 1e-12, as rng.ts does, which caps its size near 7.43, so V is a close approximation of chi-square(df), not exactly it: the exact t variance, correlation and multivariate t are the ideal draw's
- A return-inflation correlation outside [−1, 1], or not finite, is refused with a message rather than clamped
- A negative or non-finite returnVolPct or inflationVolPct is refused with a message
Provenance: derived by
claude, implemented byclaude-subagent.Independent review:
codexGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-student-t-draw.md Mutation receipt: market-model-student-t-draw.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
One-year additive user shock on a lognormal base Model record, justified by derivation
Purpose: Applies a specified percentage-point shock in one 1-based path year and a mean-preserving lognormal base in the others.
Statement: Year indices are 1-based. In the shock year the return shock is the configured shockPct; in other years it is the zero-mean lognormal 100 (exp(sigma Z − sigma^2/2) − 1). For shock year 2, shock −20, base volatility 0 and three years, the vector is [0, −20, 0]. Units: percentage points. Rounding: none. Exact.
Formula:
returnShockPct_i = shockPct if i+1 = shockYear else 100 (exp(sigma Z_i − sigma^2/2) − 1)-
shockYear: 1-based path year of the user shock (unit: years) (domain: whole number >= 1, refused otherwise; worksheet 2) -
shockPct: Additive shock in that year (unit: percentage points) (domain: worksheet −20) -
sigma: Base lognormal volatility as a decimal (unit: 1) (domain: worksheet 0)
Timing: annual; the shock year is compared as i+1
Rounding: none
Limits:
- A what-if, not an assigned crash probability
- A shockYear that is not a whole number of at least 1 is refused with a message (a value below 1 used to be raised to 1, and a fractional one gave no shock year); treating it as zero-based is the worksheet's first wrong reading
- A negative or non-finite baseReturnVolPct is refused with a message
- The shock year still consumes the year's RNG draws; only the return-shock formula is replaced
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: market-model-user-shock.md Mutation receipt: market-model-user-shock.mutation.md
Evidence tests: marketModels.evidence.test.ts
Implemented in: marketModels.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Default Monte Carlo seed and the headline run's options Data record, justified by derivation
Purpose: The one base seed every plan's default Monte Carlo draws its markets from, and the headline configuration every host can match.
Statement: DEFAULT_MONTE_CARLO_SEED = 0x5eeded = 6,221,293 (decision D-MC-DEFAULT-SEED, 2026-09-28) is the base seed of every default Monte Carlo run, the same for every plan whatever its id: the headline success rate, the Monte Carlo page until the reader re-rolls (Re-roll replaces it for that page's runs only and is never saved), every comparison the planner pairs with the headline run (the Insight preview, Scenarios, the Social Security analysis, relocation, Optimize), the risk-based guardrail threshold solve, which stores the seed beside the thresholds, and the Optimize tournament's downside-resilience metric. headlineMonteCarloOptions(plan, startYear, pathCount = 1000) publishes { startYear, pathCount, seed: DEFAULT_MONTE_CARLO_SEED, model: buildLognormalModelConfigForPlan(plan, 12) }, so a host that uses it draws the app's markets. Path i is seeded with derivePathSeed(6,221,293, i): 2,931,854,348 for path 0 and 3,741,805,609 for path 1. Before the decision the planner hashed the plan id (FNV-1a), so Duplicate, Save to My Plans and import drew new markets and two plans compared side by side ran on independent markets. Units: unsigned 32-bit integer. Rounding: none.
Formula:
seed = 0x5eeded = 6,221,293 for every plan; pathSeed_i = derivePathSeed(seed, i); headline = { pathCount 1000, seed, lognormal(plan, 12) }-
seed: Base Monte Carlo seed of a default run (unit: uint32) (domain: 6,221,293) -
i: Zero-based path index (unit: 1) (domain: integer >= 0; worksheet 0 and 1)
Timing: once per run; the page's Re-roll replaces it for that page only
Rounding: none
Limits:
- Any fixed seed is one draw from the same sampling distribution: a headline rate on 1,000 paths still carries about 1.4 points of sampling error, which one seed for every plan does not remove; it removes the id's part of it and pairs every comparison
- The value was kept because it was already the tournament's seed, before any figure was measured on it; choosing a seed by its outcomes would bias every figure
- An allocated plan draws class shocks after each year's return and inflation draws, so an allocated plan and a single-return plan share only their first year's draws even on one seed
- RetireGolden-MCP adopted these options in 0.12.0: run_monte_carlo with no arguments runs headlineMonteCarloOptions (1,000 paths, this seed, the plan's lognormal model at 12 percent), where through 0.11.x it ran 200 paths on seed 42 with a plain lognormal model; an explicit seed, pathCount or returnVolPct replaces only its own default, and returnVolPct sets only the market factor, since an allocated account draws its classes' own volatilities
- Risk-based thresholds saved before schema v6 carry no stored seed and were solved on the plan-id seed; the Spending card says so and offers to solve again (guardrailThresholdSeedBasis)
Provenance: derived by
claude, implemented byclaude.Independent review:
codexGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: monte-carlo-default-seed.md Mutation receipt: monte-carlo-default-seed.mutation.md
Evidence tests: headline.evidence.test.ts
Implemented in: rng.ts; headline.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year, insight-monte-carlo-success-delta, risk-based-guardrail-solved-balance-thresholds
-
-
The order a Monte Carlo path draws each person's death and care from Data record, justified by derivation
Purpose: Each path gives the people its random numbers in a canonical order, so listing the same household the other way round draws the same path.
Statement: montecarlo/run.ts#runMonteCarloPaths draws each path's death ages (stochastic longevity), then its care events (care shocks, a variable number of draws each), person by person in model/peopleOrder.ts#canonicalPeopleOrder: date of birth ascending (older first; ISO dates compare as strings), then sex in the written order female, male, average (CANONICAL_SEX_ORDER, never a locale comparison), then id by ordinal comparison; the sampled care events join the plan's in that order (decision D-PEOPLE-ORDER, rule R5). Before the decision the draws followed list order, so reversing a couple's people moved up to 1.5 points of success with longevity and 346 to 984 of 1,000 paths. Renaming ids moves a path only when two people share a birth date and a sex. Units: an order. Rounding: none.
Formula:
order = sort(people) by (dob ascending, index of sex in [female, male, average], id ordinal); draws: deaths in order, then care events in order-
people: The household's people (unit: records) (domain: one or two)
Timing: once per path, after the market path is drawn
Rounding: none
Limits:
- Any order is an equally valid sample; the canonical one is fixed so the published rate does not depend on which person takes the first random number, not because it is better
- Keying each person to a separate stream by id would also be order-free, but renaming ids would then move every path
Provenance: derived by
claude, implemented byclaude.Independent review:
codexGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: monte-carlo-people-draw-order.md Mutation receipt: monte-carlo-people-draw-order.mutation.md
Evidence tests: peopleDrawOrder.evidence.test.ts
Implemented in: peopleOrder.ts; run.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
SplitMix32-style per-path seed Model record, justified by derivation
Purpose: A 32-bit path seed from (base seed, zero-based path index) so a path's stream does not depend on how preceding paths consume draws.
Statement: A four-step 32-bit recurrence of the two arguments: h0 = (seed XOR imul(pathIndex + 1, 0x9e3779b9)) >>> 0; h1 = imul(h0 XOR (h0 >>> 16), 0x21f0aaad); h2 = imul(h1 XOR (h1 >>> 15), 0x735a2d97); result = (h2 XOR (h2 >>> 15)) >>> 0. For (42, 7), (42, 8) and (1, 0) the unsigned words are 1351098177, 2450979136 and 3950124170, and 1351098177 != 2450979136 so adjacent paths receive distinct seeds. Units: unsigned 32-bit integer. Rounding: none. Exact integer.
Formula:
h0 = (seed XOR imul(pathIndex+1, 0x9e3779b9)) >>> 0; h1 = imul(h0 XOR (h0>>>16), 0x21f0aaad); h2 = imul(h1 XOR (h1>>>15), 0x735a2d97); result = (h2 XOR (h2>>>15)) >>> 0-
seed: Base Monte Carlo seed (unit: uint32) (domain: worksheet 42 and 1) -
pathIndex: Zero-based path index (unit: 1) (domain: integer >= 0; worksheet 7, 8, 0) -
result: Derived path seed (unit: uint32) (domain: 0..2^32−1; worksheet 1351098177, 2450979136, 3950124170)
Timing: once per path, before that path's RNG is created
Rounding: none; 32-bit wrapping arithmetic
Limits:
- Hashing path identity rather than advancing one shared stream makes worker partitioning irrelevant
- Not cryptographic; adjacent indices are mixed by pathIndex+1 and the lowbias32 finalizer, not used as seed + index
Provenance: derived by
codex, implemented bygrok.Independent review:
grokGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: rng-derived-path-seed.md Mutation receipt: rng-derived-path-seed.mutation.md
Evidence tests: rng.evidence.test.ts
Implemented in: rng.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
-
Mulberry32 uniform reference stream Model record, justified by derivation
Purpose: The deterministic [0, 1) stream every Monte Carlo path draw is taken from.
Statement: State a is the seed as uint32. Each uniform draw: a = (a + 0x6d2b79f5) | 0; t = a; t = imul(t XOR (t >>> 15), t | 1); t ^= t + imul(t XOR (t >>> 7), t | 61); u = ((t XOR (t >>> 14)) >>> 0) / 2^32. For seed 1 the first five unsigned words are 2693262067, 11749833, 2265367787, 4213581821, 4159151403; dividing by 2^32 gives 0.6270739405881613, 0.002735721180215478, 0.5274470399599522, 0.9810509674716741, 0.9683778982143849. Standard-normal draws use Box–Muller (with a cached spare) and integers are floor(u · n) clamped into [0, n). Units: dimensionless in [0, 1). Rounding: none. Exact integer words; relative tolerance 1e-15 if doubles are compared.
Formula:
a ← (a + 0x6d2b79f5) | 0; avalanche; word = uint32(t); u = word / 2^32-
seed: Initial state (unit: uint32) (domain: worksheet 1) -
word: Unsigned 32-bit Mulberry32 output (unit: uint32) (domain: worksheet [2693262067, 11749833, 2265367787, 4213581821, 4159151403]) -
u: Uniform draw in [0, 1) (unit: 1) (domain: [0, 1))
Timing: one state advance per uniform; Box–Muller consumes two uniforms per pair of normals
Rounding: none; divide by 2^32 not 2^32−1
Limits:
- A small noncryptographic generator suitable for reproducible simulation sampling, not security
- Normal draws cache the paired Box–Muller value; the worksheet vector is the uniform stream, not the normal stream
Provenance: derived by
codex, implemented bygrok.Independent review:
cursorGates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass
Worksheet: rng-mulberry32-reference-stream.md Mutation receipt: rng-mulberry32-reference-stream.mutation.md
Evidence tests: rng.evidence.test.ts
Implemented in: rng.ts
Feeds: monte-carlo-success-rate, monte-carlo-investable-fan-percentiles, monte-carlo-ending-investable-histogram, monte-carlo-ending-after-tax-estate-percentiles, monte-carlo-depletion-probability-by-year
-
Engine source: engine/src/montecarlo/run.ts
(MonteCarloSummary.depletionProbabilityByYear[].probability / cumulativeProbability)
The evidence links on this page (each record's worksheet, mutation receipt, evidence tests
and implementation, and the source links) are pinned to engine commit
2624dbdba9fd, committed 2026-10-11.
The live calculation index and
output-family census links point at the repository's main
branch, so you can check this snapshot against them.