From 3cabe0d658957eddd5799013570cf134e9d0a185 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 19:51:53 +0000 Subject: [PATCH 1/4] feat(builder): composable scale node for per-type damage transforms Add a general `scale` AST node plus `RollBuilder.scaleResult(numerator, denominator, rounding)` and a `sumRolls(parts)` composite factory. Unlike the fixed `half()` node, a scaled sub-roll can be combined with plain rolls via `sumRolls` without the flat-config `.plus()` merge silently dropping it, so the scaling survives into both the PMF and the rendered expression. Renders as `N * (expr)` (e.g. vulnerability, `2 * (1d6)`), `(expr) // D` (e.g. resistance, `(1d6) // 2`), or `(expr) * N // D`. Enables per-damage-type resistance / immunity / vulnerability in downstream consumers: build one sub-roll per damage type, scale (or omit) each, and `sumRolls` them into a single hit/crit/save payload that convolves per-type PMFs (so `floor` stays per-type) while attribution and outcome mixing flow through the existing path. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX --- src/builder/ast.ts | 11 +++ src/builder/nodes.ts | 20 +++++- src/builder/roll.ts | 138 ++++++++++++++++++++++++++++++++++++++ src/builder/scale.test.ts | 118 ++++++++++++++++++++++++++++++++ 4 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 src/builder/scale.test.ts diff --git a/src/builder/ast.ts b/src/builder/ast.ts index 005f5dd..2c76953 100644 --- a/src/builder/ast.ts +++ b/src/builder/ast.ts @@ -218,6 +218,12 @@ export function resolve(node: ExpressionNode, eps: number = defaultEps): PMF { // Compute the maximum of count independent rolls of childPMF return computeMaxOfPMF(childPMF, count, eps); } + + case "scale": { + const childPMF = resolve(node.child, eps); + const denom = node.denominator === 0 ? 1 : node.denominator; + return childPMF.scaleDamage(node.numerator / denom, node.rounding); + } } })(); @@ -318,6 +324,7 @@ function findDie(node: ExpressionNode): DieNode | undefined { case "d20Roll": case "half": case "maxOf": + case "scale": return findDie(node.child); case "keep": return findDie(node.child.child); @@ -581,6 +588,10 @@ export function getASTSignature(node: ExpressionNode): string { return `half{ch:${getASTSignature(node.child)}}`; case "maxOf": return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`; + case "scale": + return `scale{n:${node.numerator},d:${node.denominator},r:${ + node.rounding + },ch:${getASTSignature(node.child)}}`; case "add": { let constantValue = 0; const otherChildrenSigs: string[] = []; diff --git a/src/builder/nodes.ts b/src/builder/nodes.ts index d947743..d70e415 100644 --- a/src/builder/nodes.ts +++ b/src/builder/nodes.ts @@ -6,7 +6,8 @@ export type ExpressionNode = | KeepNode | D20RollNode | HalfNode - | MaxOfNode; + | MaxOfNode + | ScaleNode; export type DieNode = { type: "die"; @@ -56,3 +57,20 @@ export type MaxOfNode = { count: number; child: ExpressionNode; }; + +/** + * Scale the child's result by `numerator / denominator`, then round. + * + * Unlike {@link HalfNode} (a fixed `// 2` with floor), this is a general, composable + * multiplier/divider — the building block for damage-type resistance (`1/2`, floor), + * vulnerability (`2/1`), and similar per-source transforms. It renders as + * `N * (child)` when the denominator is 1, `(child) // D` when the numerator is 1, + * and `(child) * N // D` otherwise. + */ +export type ScaleNode = { + type: "scale"; + numerator: number; + denominator: number; + rounding: "floor" | "round" | "ceil"; + child: ExpressionNode; +}; diff --git a/src/builder/roll.ts b/src/builder/roll.ts index 170a495..b72d8b2 100644 --- a/src/builder/roll.ts +++ b/src/builder/roll.ts @@ -716,6 +716,20 @@ export class RollBuilder { return new HalfRollBuilder(this); } + /** + * Scale this roll's result by `numerator / denominator`, rounding each outcome. + * A general, composable form of {@link half} — used to model damage-type resistance + * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). + * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. + */ + scaleResult( + numerator: number, + denominator: number = 1, + rounding: "floor" | "round" | "ceil" = "floor" + ): ScaleRollBuilder { + return new ScaleRollBuilder(this, numerator, denominator, rounding); + } + // Create a "max of N rolls" version of this roll for crit damage with keep operations maxOf(count: number): MaxOfRollBuilder { return new MaxOfRollBuilder(this, count); @@ -783,6 +797,64 @@ export class HalfRollBuilder extends RollBuilder { } } +/** + * A roll whose result is scaled by `numerator / denominator` and rounded — the composable + * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or + * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls. + */ +export class ScaleRollBuilder extends RollBuilder { + constructor( + private readonly innerRoll: RollBuilder, + private readonly numerator: number, + private readonly denominator: number = 1, + private readonly rounding: "floor" | "round" | "ceil" = "floor" + ) { + super(0); // dummy, we override methods + } + + override hasHiddenState(): boolean { + return this.innerRoll.hasHiddenState(); + } + + override get lastConfig(): RollConfig { + return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig; + } + + getSubRollConfigs(): readonly RollConfig[] { + return this.innerRoll.getSubRollConfigs(); + } + + toExpression(): string { + const inner = this.innerRoll.toExpression(); + if (this.denominator === 1) return `${this.numerator} * (${inner})`; + if (this.numerator === 1) return `(${inner}) // ${this.denominator}`; + return `(${inner}) * ${this.numerator} // ${this.denominator}`; + } + + toAST(): ExpressionNode { + return { + type: "scale", + numerator: this.numerator, + denominator: this.denominator, + rounding: this.rounding, + child: this.innerRoll.toAST(), + }; + } + + toPMF(eps: number = 0): PMF { + return pmfFromRollBuilder(this, eps); + } + + copy(): ScaleRollBuilder { + return new ScaleRollBuilder( + this.innerRoll.copy(), + this.numerator, + this.denominator, + this.rounding + ); + } +} + export class MaxOfRollBuilder extends RollBuilder { constructor( private readonly innerRoll: RollBuilder, @@ -1206,3 +1278,69 @@ export class PooledRollBuilder extends RollBuilder { return new PooledRollBuilder(sumNode, newExpr); } } + +/** + * An additive composite of independent rolls that preserves each part's AST — the piece + * that lets a scaled/halved sub-roll (which the flat `.plus()` merge would otherwise drop) + * sit beside plain rolls in one damage payload. Its PMF convolves the parts; its expression + * joins them with ` + `. Built via {@link sumRolls}; terminal (used as an onHit/onCrit/ + * onSaveFailure payload), so it reports hidden state to reject accidental flat merges. + */ +class CompositeSumRollBuilder extends RollBuilder { + constructor(private readonly parts: readonly RollBuilder[]) { + super(0); // dummy, we override methods + } + + override hasHiddenState(): boolean { + return true; + } + + override getSubRollConfigs(): readonly RollConfig[] { + return []; + } + + override toAST(): ExpressionNode { + return { + type: "add", + children: this.parts.map((p) => ({ + node: p.toAST(), + sign: 1 as const, + })), + }; + } + + override toExpression(): string { + const exprs = this.parts + .map((p) => p.toExpression()) + .filter((e) => e && e !== "0"); + if (exprs.length === 0) return "0"; + let result = exprs[0]; + for (let i = 1; i < exprs.length; i++) { + const e = exprs[i]; + result += e.startsWith("-") ? ` - ${e.substring(1)}` : ` + ${e}`; + } + return result.replace(/\+ -/g, "-"); + } + + override toPMF(eps: number = 0): PMF { + return pmfFromRollBuilder(this, eps); + } + + override copy(): CompositeSumRollBuilder { + return new CompositeSumRollBuilder(this.parts.map((p) => p.copy())); + } +} + +/** + * Combine several rolls into one additive payload whose PMF is their convolution and whose + * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry + * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance + * and vulnerability survive into both the distribution and the rendered expression. + * Empty parts collapse to `0`; a single part is returned unwrapped. + */ +export function sumRolls(parts: readonly RollBuilder[]): RollBuilder { + const meaningful = parts.filter((p): p is RollBuilder => p !== undefined); + if (meaningful.length === 0) return new RollBuilder(0); + if (meaningful.length === 1) return meaningful[0]; + return new CompositeSumRollBuilder(meaningful); +} diff --git a/src/builder/scale.test.ts b/src/builder/scale.test.ts new file mode 100644 index 0000000..a5f44da --- /dev/null +++ b/src/builder/scale.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from "vitest"; +import { d, d20 } from "../builder"; +import { RollBuilder, ScaleRollBuilder, sumRolls } from "./roll"; + +const mean = (pmf: { map: Map }): number => { + let m = 0; + for (const [v, b] of pmf.map.entries()) m += v * b.p; + return m; +}; + +const distEntries = (pmf: { map: Map }) => + [...pmf.map.entries()].sort((a, b) => a[0] - b[0]).map(([v, b]) => [v, b.p]); + +const d6 = () => new RollBuilder().plus(1, d(6)); + +describe("scaleResult / ScaleRollBuilder", () => { + it("halves (floor) a single die: expression and distribution", () => { + const half = d6().scaleResult(1, 2); + expect(half).toBeInstanceOf(ScaleRollBuilder); + expect(half.toExpression()).toBe("(1d6) // 2"); + // floor(1..6 / 2) => 0,1,1,2,2,3 + expect(distEntries(half.pmf)).toEqual([ + [0, 1 / 6], + [1, 2 / 6], + [2, 2 / 6], + [3, 1 / 6], + ]); + expect(mean(half.pmf)).toBeCloseTo(1.5, 10); + }); + + it("matches the existing half() node for the // 2 case", () => { + expect(d6().scaleResult(1, 2).toExpression()).toBe(d6().half().toExpression()); + expect(mean(d6().scaleResult(1, 2).pmf)).toBeCloseTo(mean(d6().half().pmf), 10); + }); + + it("doubles the total for vulnerability (2 * (expr))", () => { + const vuln = d6().scaleResult(2); + expect(vuln.toExpression()).toBe("2 * (1d6)"); + // 2 * (1..6) => even values 2..12, each 1/6 (NOT triangular like 2d6) + expect(distEntries(vuln.pmf)).toEqual([ + [2, 1 / 6], + [4, 1 / 6], + [6, 1 / 6], + [8, 1 / 6], + [10, 1 / 6], + [12, 1 / 6], + ]); + expect(mean(vuln.pmf)).toBeCloseTo(7, 10); + }); + + it("renders a general numerator/denominator form", () => { + expect(d6().scaleResult(3, 4).toExpression()).toBe("(1d6) * 3 // 4"); + }); + + it("supports round and ceil rounding modes", () => { + // ceil(1..6 / 2) => 1,1,2,2,3,3 => mean 2 + expect(mean(d6().scaleResult(1, 2, "ceil").pmf)).toBeCloseTo(2, 10); + }); +}); + +describe("sumRolls", () => { + it("returns a single part unwrapped and empty as 0", () => { + const base = new RollBuilder().plus(3).plus(1, d(8)); + expect(sumRolls([base])).toBe(base); + expect(sumRolls([]).toExpression()).toBe("0"); + }); + + it("preserves a scaled part beside a plain part (which plus() would drop)", () => { + const base = new RollBuilder().plus(3).plus(1, d(8)); // 1d8 + 3, mean 7.5 + const resistedFire = d6().scaleResult(1, 2); // (1d6)//2, mean 1.5 + const payload = sumRolls([base, resistedFire]); + + expect(payload.toExpression()).toBe("1d8 + 3 + (1d6) // 2"); + expect(mean(payload.pmf)).toBeCloseTo(9.0, 10); + + // Guard: the flat merge silently drops the scaled part. + expect(base.plus(resistedFire).toExpression()).toBe("1d8 + 3"); + }); + + it("only scales the resisted damage type in a mixed attack (floor is per-type)", () => { + // 1d8 slashing (unresisted) + 1d6 fire (resisted). Halving the WHOLE total would be wrong. + const slashing = new RollBuilder().plus(1, d(8)); // mean 4.5 + const fireResisted = d6().scaleResult(1, 2); // mean 1.5 + const perType = sumRolls([slashing, fireResisted]); + expect(mean(perType.pmf)).toBeCloseTo(6.0, 10); + + // Halving the merged total instead would give floor((1d8+1d6)/2) => mean ~3.79, clearly different. + const wholeHalved = new RollBuilder().plus(1, d(8)).plus(1, d(6)).scaleResult(1, 2); + expect(mean(wholeHalved.pmf)).not.toBeCloseTo(6.0, 1); + }); + + it("carries resistance through a full attack's hit and crit payloads + keeps attribution", () => { + const hit = sumRolls([ + new RollBuilder().plus(3).plus(1, d(8)), + d6().scaleResult(1, 2), + ]); + const crit = sumRolls([ + new RollBuilder().plus(3).plus(2, d(8)), + new RollBuilder().plus(2, d(6)).scaleResult(1, 2), + ]); + const attack = d20.ac(15).onHit(hit).onCrit(crit); + + expect(attack.toExpression()).toBe( + "(d20 AC 15) * (1d8 + 3 + (1d6) // 2) crit (2d8 + 3 + (2d6) // 2)" + ); + + const totals: Record = {}; + for (const [, b] of attack.pmf.map.entries()) { + for (const [k, p] of Object.entries(b.count ?? {})) { + totals[k] = (totals[k] ?? 0) + (p as number); + } + } + // 0.7 miss / 0.25 hit / 0.05 crit at AC 15 with a flat d20. + expect(totals.missNone).toBeCloseTo(0.7, 6); + expect(totals.hit).toBeCloseTo(0.25, 6); + expect(totals.crit).toBeCloseTo(0.05, 6); + }); +}); From b6190dce3b2885230021a372d16d05212f3a52c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 19:54:58 +0000 Subject: [PATCH 2/4] chore(build): vendor built dist for pre-publish git consumption `dist/` is normally gitignored and produced by `prepare` on npm install. Committing the built output on this branch lets a consumer install it as a git dependency (before 0.4 is published to npm) without running the build. Revert once @yipe/dice@0.4 is published and consumers pin `^0.4.0`. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX --- dist/builder/index.cjs | 5501 ++++++++++++++++++++++++++++++++++++ dist/builder/index.cjs.map | 1 + dist/builder/index.d.cts | 429 +++ dist/builder/index.d.ts | 429 +++ dist/builder/index.js | 5474 +++++++++++++++++++++++++++++++++++ dist/builder/index.js.map | 1 + dist/index.cjs | 3627 ++++++++++++++++++++++++ dist/index.cjs.map | 1 + dist/index.d.cts | 111 + dist/index.d.ts | 111 + dist/index.js | 3601 +++++++++++++++++++++++ dist/index.js.map | 1 + dist/pmf-D5VRghZI.d.cts | 1129 ++++++++ dist/pmf-D5VRghZI.d.ts | 1129 ++++++++ 14 files changed, 21545 insertions(+) create mode 100644 dist/builder/index.cjs create mode 100644 dist/builder/index.cjs.map create mode 100644 dist/builder/index.d.cts create mode 100644 dist/builder/index.d.ts create mode 100644 dist/builder/index.js create mode 100644 dist/builder/index.js.map create mode 100644 dist/index.cjs create mode 100644 dist/index.cjs.map create mode 100644 dist/index.d.cts create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/pmf-D5VRghZI.d.cts create mode 100644 dist/pmf-D5VRghZI.d.ts diff --git a/dist/builder/index.cjs b/dist/builder/index.cjs new file mode 100644 index 0000000..bd3d208 --- /dev/null +++ b/dist/builder/index.cjs @@ -0,0 +1,5501 @@ +'use strict'; + +// src/common/lru-cache.ts +var LRUCache = class { + constructor(maxSize = 1e3) { + this.maxSize = maxSize; + this.cache = /* @__PURE__ */ new Map(); + } + get(key) { + const value = this.cache.get(key); + if (value === void 0) return void 0; + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + delete(key) { + this.cache.delete(key); + } + set(key, value) { + if (this.cache.size >= this.maxSize && !this.cache.has(key)) { + const oldestKey = this.cache.keys().next().value; + this.cache.delete(oldestKey); + } + this.cache.delete(key); + this.cache.set(key, value); + return this; + } + clear() { + this.cache.clear(); + } + get size() { + return this.cache.size; + } + has(key) { + return this.cache.has(key); + } + keys() { + return this.cache.keys(); + } + values() { + return this.cache.values(); + } +}; + +// src/common/types.ts +var EPS = 1e-12; +var MISS_NONE_OUTCOME = "missNone"; + +// src/pmf/query.ts +var _DiceQuery = class _DiceQuery { + constructor(singles, combined, eps = EPS) { + this.singles = Array.isArray(singles) ? singles : [singles]; + if (this.singles.some((s) => s === void 0)) { + throw new Error("DiceQuery contains undefined singles"); + } + this._eps = eps; + this._combinedProvided = combined !== void 0; + if (combined !== void 0) { + this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); + } + } + /** + * The combined damage distribution of all single PMFs (their convolution), + * normalized to total probability 1. + * + * Computed lazily on first access and cached. Queries that only need + * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, + * {@link DiceQuery.stddev} — never trigger this convolution. + */ + get combined() { + if (this._combined === void 0) { + const c = PMF.convolveMany(this.singles); + this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); + } + return this._combined; + } + /** + * Returns a new PMF with damage attribution metadata populated. + * + * This method computes attribution on-demand for builder-generated PMFs, + * enabling them to work with damage attribution charts. The `attr` field + * tracks how much damage each outcome type contributes at each damage value. + * + * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) + * + * Performance: Cached after first call. Adds minimal overhead vs `combined`. + * + * @returns PMF with attr field populated for damage attribution charts + * + * @example + * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) + * const query = attack.toQuery() + * const pmf = query.combinedWithAttribution() + * // Now pmf can be used with toDamageAttributionChartSeries() + */ + combinedWithAttribution() { + if (this._combinedWithAttr) { + return this._combinedWithAttr; + } + if (this.singles.every((pmf) => pmf.hasAttribution())) { + this._combinedWithAttr = this.combined; + return this._combinedWithAttr; + } + const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); + const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); + const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); + this._combinedWithAttr = normalized; + return normalized; + } + /** + * Per-label `damage value → probability mass` series for the combined, + * attribution-carrying distribution — the provenance core of the stacked + * damage-attribution chart. Convenience for + * `combinedWithAttribution().attributionByValue()`; see + * {@link PMF.attributionByValue}. + */ + attributionByValue() { + return this.combinedWithAttribution().attributionByValue(); + } + /** + * How many of the independent single PMFs can produce the given outcome + * label. Useful for "all of them succeeded" style probabilities where the + * exponent is the number of contributing attacks (see + * {@link DiceQuery.probExactlyK}). + */ + countSinglesWith(label) { + let count = 0; + for (const single of this.singles) { + if (single.hasOutcome(label)) count++; + } + return count; + } + /** + * Returns the expected damage across all possible outcomes. + * + * Example: `query.mean()` → 12.5 + * Use case: "What's my average damage per round?" + */ + mean() { + if (this._combinedProvided) { + let m = 0; + for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; + return m; + } + let totalMean = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; + } + return totalMean; + } + /** + * Returns the variance of the damage distribution. + * + * Example: `query.variance()` → 45.2 + * Use case: "How much does my damage vary from the average?" + * High variance means higher risk/reward. Lower variance means more consistent damage. + */ + variance() { + if (this._combinedProvided) { + const mu = this.mean(); + let v = 0; + for (const [damageValue, bin] of this.combined) { + const dev = damageValue - mu; + v += dev * dev * bin.p; + } + return v; + } + let totalVariance = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + if (Math.abs(mass - 1) <= this._eps) { + totalVariance += single.variance(); + } else { + let mu = 0; + for (const [d2, b] of single) mu += d2 * (b.p / mass); + let v = 0; + for (const [d2, b] of single) { + const dev = d2 - mu; + v += dev * dev * (b.p / mass); + } + totalVariance += v; + } + } + return totalVariance; + } + /** + * Returns the standard deviation of the damage distribution. + * + * Example: `query.stdev()` → 6.7 + * Use case: "What's the typical spread around my average damage?" + * Used to determine how consistent the damage is. + */ + stddev() { + return Math.sqrt(this.variance()); + } + /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ + stdev() { + return this.stddev(); + } + /** + * Returns the Cumulative Distribution Function. + */ + cdf(x) { + return this.probTotalAtMost(x); + } + /** + * Returns the probability of dealing X damage or less. + * In statistics, this is called the cumulative distribution function (CDF). + * Example: `query.cdf(20)` → 0.75 + * Use case: "What's the chance I deal 20 damage or less?" + */ + probTotalAtMost(x) { + let cumulativeProbability = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue <= x) { + cumulativeProbability += probabilityBin.p; + } + } + return cumulativeProbability; + } + /** + * Returns the Complementary Cumulative Distribution Function. + */ + ccdf(x) { + return this.probTotalAtLeast(x); + } + /** + * Returns the probability of dealing at least X damage. + * + * Example: `query.probTotalAtLeast(25)` → 0.35 + * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" + */ + probTotalAtLeast(threshold) { + let probabilitySum = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue >= threshold) { + probabilitySum += probabilityBin.p; + } + } + return probabilitySum; + } + /** + * Returns damage values at specific percentiles. + * + * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] + * Use case: "What are my 25th, 50th, and 75th percentile damage values?" + */ + percentiles(percentileValues) { + const sortedDamageValues = this.combined.support(); + if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); + const cumulativeProbabilities = []; + let runningProbabilitySum = 0; + for (const damageValue of sortedDamageValues) { + runningProbabilitySum += this.combined.map.get(damageValue).p; + cumulativeProbabilities.push(runningProbabilitySum); + } + return percentileValues.map((targetPercentile) => { + let leftBound = 0; + let rightBound = cumulativeProbabilities.length - 1; + while (leftBound <= rightBound) { + const middleIndex = Math.floor((leftBound + rightBound) / 2); + if (cumulativeProbabilities[middleIndex] >= targetPercentile) { + rightBound = middleIndex - 1; + } else { + leftBound = middleIndex + 1; + } + } + return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; + }); + } + /** + * Returns the minimum possible damage. + * + * Example: `query.min()` → 0 + * Use case: "What's the worst-case damage if everything misses?" + */ + min() { + return this.combined.min(); + } + /** + * Returns the maximum possible damage. + * + * Example: `query.max()` → 56 + * Use case: "What's the best-case damage if everything crits and rolls max?" + */ + max() { + return this.combined.max(); + } + singleProb(diceIndex, label) { + const single = this.singles[diceIndex]; + let probabilitySum = 0; + for (const [, probabilityBin] of single) { + probabilitySum += probabilityBin.count[label] || 0; + } + const mass = single.mass(); + return mass > 0 ? probabilitySum / mass : 0; + } + /** + * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it + * carries ANY of `labels`", over the n independent singles. + * + * Each single's per-event success probability is the Poisson-binomial + * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), + * computed exactly once. The binomial DP then runs once to produce the whole + * distribution, so the array-label paths of probExactlyK / probAtLeastK / + * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and + * re-running the DP per requested k. + */ + countDistribution(labels) { + const n = this.singles.length; + const successProbabilities = this.singles.map( + (single) => new _DiceQuery([single]).probabilityOf(labels) + ); + const dist = new Array(n + 1).fill(0); + dist[0] = 1; + for (const successProb of successProbabilities) { + for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { + dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; + } + dist[0] *= 1 - successProb; + } + return dist; + } + probAtLeastK(labels, k) { + const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; + const n = this.singles.length; + if (k <= 0) return 1; + if (k > n) return 0; + const dist = this.countDistribution(L); + let tail = 0; + for (let i = k; i <= n; i++) { + tail += dist[i]; + } + if (tail < 0) return 0; + if (tail > 1) return 1; + return tail; + } + /** + * Returns the probability that at least one attack has the specified outcome(s). + * - This is the complement of probAtMostK(labels, 0) + * + * Examples: + * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) + * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) + * + * Use cases: + * - "What's the chance at least one of my attacks connects?" + * + * Note: + * + * - You have to pass in an array of labels to avoid double-counting if you are + * using multiple labels. You cannot just add them. + */ + probAtLeastOne(labels) { + if (typeof labels === "string") { + labels = [labels]; + } + let productOfNonOccurrence = 1; + for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { + let combinedProbability = 0; + for (const label of labels) { + combinedProbability += this.singleProb(diceIndex, label); + } + if (combinedProbability < 0) combinedProbability = 0; + else if (combinedProbability > 1) combinedProbability = 1; + productOfNonOccurrence *= 1 - combinedProbability; + } + const result = 1 - productOfNonOccurrence; + return result < 0 ? 0 : result > 1 ? 1 : result; + } + /** + * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. + * + * Uses dynamic programming to efficiently calculate the probability distribution + * of how many attacks will have the specified outcome, accounting for different + * success probabilities across individual attacks. + * + * Example: For 3 attacks with 50% hit chance each, returns: + * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] + * + * @param label - The outcome type to count + * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) + * @returns Array where index K contains P(exactly K attacks have the label) + */ + computeBinomialProbabilities(label, maxK) { + const individualProbabilities = this.singles.map( + (_, diceIndex) => this.singleProb(diceIndex, label) + ); + const binomialProbs = new Array(maxK + 1).fill(0); + binomialProbs[0] = 1; + for (const singleProbability of individualProbabilities) { + for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { + binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; + } + binomialProbs[0] *= 1 - singleProbability; + } + return binomialProbs; + } + /** + * Returns the probability that exactly K attacks result in the specified outcome(s). + * + * Single label examples: + * - probExactlyK('hit', 2) = probability exactly 2 attacks hit + * - probExactlyK('crit', 1) = probability exactly 1 attack crits + * - probExactlyK('crit', 0) = probability no attacks crit + * + * Array examples: + * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed + * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds + * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss + * + * Use cases: + * - "What's the chance exactly one of my attacks hits?" + * - "How likely am I to get exactly 2 successes out of 3 attacks?" + * - "What's the probability that exactly half my attacks succeed?" + * + * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. + * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. + */ + probExactlyK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + return probabilityArray[k]; + } + const dist = this.countDistribution(labels); + return k >= 0 && k < dist.length ? dist[k] : 0; + } + /** + * Returns the probability that AT MOST K attacks result in the specified outcome(s). + * + * Single label examples: + * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) + * - probAtMostK('crit', 0) = probability no attacks crit + * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss + * + * Array examples: + * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds + * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) + * + * Use cases: + * - "What's the chance that at most one attack hits?" (rest miss) + * - "How likely am I to have mostly failures?" (at most 1 success) + * - "What's the probability of a really bad turn?" (at most 0 successes) + * + */ + probAtMostK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + let cumulativeSum2 = 0; + for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { + cumulativeSum2 += probabilityArray[outcomeCount]; + } + return cumulativeSum2; + } + const dist = this.countDistribution(labels); + const upper = Math.min(k, dist.length - 1); + let cumulativeSum = 0; + for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { + cumulativeSum += dist[outcomeCount]; + } + return cumulativeSum; + } + /** + * Returns the expected damage attributed to specific outcome types. + * + * Single label examples: + * - expectedDamageFrom('hit') = expected damage from hit components + * - expectedDamageFrom('crit') = expected damage from crit components + * + * Array examples: + * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success + * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses + * + * Use cases: + * - "How much damage do I expect from successful attacks?" + * - "What's the damage contribution from critical hits specifically?" + * - "How much damage comes from miss effects (like save-for-half spells)?" + */ + expectedDamageFrom(labels) { + const wanted = Array.isArray(labels) ? labels : [labels]; + let total = 0; + for (const single of this.singles) { + for (const [dmg, bin] of single) { + let p = 0; + for (const label of wanted) p += bin.count[label] ?? 0; + total += dmg * p; + } + } + return total; + } + /** + * Returns damage statistics for scenarios where AT LEAST ONE attack results in + * the specified outcome(s). + * + * This method answers "What happens when things go reasonably well?" rather than + * "What's the theoretical maximum?" It includes mixed scenarios which are more + * common and tactically relevant than pure scenarios. + * + * Single label examples: + * - damageStatsFrom('hit') = damage range when at least one attack hits + * - damageStatsFrom('crit') = damage range when at least one attack crits + * + * Array examples: + * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds + * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses + * + * Tactical Use Cases: + * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" + * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" + * - "Should I use this risky spell if it has good damage when it works?" + * - "What's my damage potential when something goes right?" (vs pure failure) + * + * Combat Planning Examples: + * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" + * (Much more useful than "You average 50 damage including complete misses") + * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" + * - Resource management: "If I hit anything, I'll likely finish this enemy" + * + * Statistical Note: + * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which + * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. + * + * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an + * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and + * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than + * E[dmg | the label occurs]. For a single attack both are the plain + * conditional figures. Use {@link probAtLeastOne} for the scenario probability. + * + * @example + * // High-level tactical planning + * const successStats = query.damageStatsFrom('hit') + * const successChance = query.probAtLeastOne('hit') + * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) + */ + damageStatsFrom(labels) { + const labelArray = typeof labels === "string" ? [labels] : labels; + let minDamage = Infinity; + let maxDamage = -Infinity; + let totalDamage = 0; + let totalCount = 0; + for (const [damage, probabilityBin] of this.combined) { + let binHasAnyLabel = false; + let binContribution = 0; + for (const label of labelArray) { + const count = probabilityBin.count[label]; + if (count && count > 0) { + binHasAnyLabel = true; + binContribution += count; + } + } + if (damage > 0 && binHasAnyLabel) { + minDamage = Math.min(minDamage, damage); + maxDamage = Math.max(maxDamage, damage); + const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; + totalDamage += damage * weightToUse; + totalCount += weightToUse; + } + } + return { + min: minDamage === Infinity ? 0 : minDamage, + max: maxDamage === -Infinity ? 0 : maxDamage, + avg: totalCount > 0 ? totalDamage / totalCount : 0, + count: totalCount + }; + } + /** + * Returns damage statistics for scenarios where ALL attacks result in the specified + * outcome, calculated by leveraging the pure partition of singles. + * + * This method answers "What's the theoretical best/worst case?" and "What are the + * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. + * + * Examples: + * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) + * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) + * + * UI and Display Use Cases: + * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) + * - "Best case scenario" vs "worst case scenario" analysis + * - Mathematical verification: "Does our hit damage calculation match manual math?" + * - Clean damage type attribution: "How much comes from base hits vs crits?" + * + * Design and Balance Use Cases: + * - Game designers: "What's the damage ceiling if someone gets lucky?" + * - Character optimization: "What's my absolute maximum potential?" + * - Ability comparison: "Which build has higher crit ceiling?" + * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" + * + * Mathematical Use Cases: + * - Validating complex calculations against simple manual math + * - Understanding damage component contributions in isolation + * - Separating luck (crit variance) from consistency (hit variance) + * - Building intuition about damage sources + * + * When to Use This vs damageStatsFrom(): + * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons + * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios + * + * Statistical Note: + * Pure scenarios (all hits, all crits) are rare but represent clear mathematical + * boundaries. These stats help understand the "shape" of your damage potential. + * + * @example + * // UI display logic + * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" + * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" + * + * // vs tactical planning (use damageStatsFrom instead) + * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios + */ + combinedDamageStats(targetLabel) { + const singleStats = this.singles.map( + (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) + ); + if (singleStats.some((stats) => stats.count === 0)) { + return { min: 0, max: 0, avg: 0, count: 0 }; + } + const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); + const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); + const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); + const combinedProb = singleStats.reduce( + (product, stats) => product * stats.count, + 1 + ); + return { + min: combinedMin, + max: combinedMax, + avg: combinedAvg, + count: combinedProb + }; + } + /** + * Returns the probability that at least one attack carries ANY of the + * specified labels (the marginal P(≥1) across the independent attacks). + * + * Examples: + * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) + * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) + * + * Use cases: + * - "What's the chance my resolution includes a success label?" + * - "How likely am I to get any hits or crits across all attacks?" + * + * Note: this must NOT be computed by summing `combined` bin probabilities. A + * single combined damage total is reachable by many outcome combinations and + * a bin can hold several labels at once, so summing `bin.p` over bins that + * contain a label over-counts. The correct marginal is the Poisson-binomial + * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. + */ + probabilityOf(labels) { + return this.probAtLeastOne(labels); + } + /** + * Returns the probability of missing (any type of miss). + * + * Example: `query.missChance()` → 0.04 + * Use case: "What's the chance I miss completely this turn?" + */ + missChance() { + return this.probabilityOf(["missDamage", "missNone"]); + } + /** + * Returns data formatted for plotting damage probability distribution. + * + * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] + * Use case: "I want to visualize my damage distribution in a chart." + */ + toChartSeries() { + return this.combined.support().map((damageValue) => ({ + x: damageValue, + y: this.combined.map.get(damageValue).p + })); + } + /** + * Returns tabular data showing damage values and their probability breakdowns. + * + * Example: `query.toLabeledTable(['hit', 'crit'])` → + * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] + * + * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." + */ + toLabeledTable(labels = []) { + return this.combined.support().map((damageValue) => { + const probabilityBin = this.combined.map.get(damageValue); + const tableRow = { + damage: damageValue, + total: probabilityBin.p + }; + for (const outcomeLabel of labels) { + tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; + } + return tableRow; + }); + } + /** + * Returns data for stacked charts with unconditional per-label probability mass per damage. + * + * - Each dataset value equals the unconditional probability mass for that label at that damage + * (i.e., `bin.count[label]`). + * - Column sums may be less than the total probability `bin.p` when you omit labels or when + * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. + * - This behavior matches tests that expect raw per-label mass (not proportional scaling). + * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. + * + * @example + * query.toStackedChartData(['hit', 'crit']) + * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} + */ + toStackedChartData(labels = [], epsilon = EPS) { + const damageValues = this.combined.support(); + const datasets = labels.map((outcomeLabel) => ({ + label: outcomeLabel, + data: damageValues.map((dmg) => { + const bin = this.combined.map.get(dmg); + const v = bin ? bin.count[outcomeLabel] || 0 : 0; + return v <= epsilon ? 0 : v; + }) + })); + return { labels: damageValues, datasets }; + } + /** + * Returns pure mathematical data for attribution charts showing outcome contributions. + * + * Automatically discovers all outcome types present in the PMF, applies filtering rules, + * and returns proportional data suitable for stacked visualization. + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and proportional data + * + * @example + * query.toAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} + */ + toAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeCount = bin.count[outcome] || 0; + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + const outcomeProbability = bin.p * outcomeFraction; + return asPercentages ? outcomeProbability * 100 : outcomeProbability; + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for damage attribution charts showing damage contribution + * from each outcome type at each damage value. + * + * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of + * bin.count (probability attribution). + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) + * @returns Pure data structure with support, outcomes, and damage attribution data + * + * @example + * query.toDamageAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} + */ + toDamageAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + if (bin.attr) { + for (const outcomeType in bin.attr) { + if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin || !bin.attr) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeDamageAttribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { + if (filterRules(outcomeName, damage)) { + totalDamageAttribution += damageAttr || 0; + } + } + if (totalDamageAttribution === 0) return 0; + const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; + return damagePercentage * bin.p * 100; + } else { + return outcomeDamageAttribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for outcome attribution charts showing which + * attack outcome combinations can produce each damage value. + * + * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks + * outcome combinations - answering "what attack outcomes produced this damage?" + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and outcome combination probabilities + * + * @example + * query.toOutcomeAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} + */ + toOutcomeAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + if (outcome === "missNone") { + const outcomeCount = bin.count[outcome] || 0; + if (outcomeCount === 0) return 0; + if (asPercentages) { + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + return outcomeFraction * bin.p * 100; + } else { + return outcomeCount; + } + } + if (!bin.attr) return 0; + const outcomeDamageContribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [, damageAttr] of Object.entries(bin.attr)) { + totalDamageAttribution += damageAttr || 0; + } + if (totalDamageAttribution === 0) return 0; + const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; + return outcomeFraction * bin.p * 100; + } else { + return outcomeDamageContribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for cumulative distribution function (CDF). + * Shows P(X ≤ x) - the probability of getting at most x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and cumulative probabilities + * + * @example + * query.toCDFSeries() + * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} + */ + toCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const cdfData = []; + for (const damage of support) { + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + cdfData.push( + asPercentages ? cumulativeProbability * 100 : cumulativeProbability + ); + } + return { + support, + data: cdfData + }; + } + /** + * Returns pure mathematical data for complementary cumulative distribution function (CCDF). + * Shows P(X ≥ x) - the probability of getting at least x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and complementary cumulative probabilities + * + * @example + * query.toCCDFSeries() + * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} + */ + toCCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const ccdfData = []; + for (const damage of support) { + const ccdf = 1 - cumulativeProbability; + ccdfData.push(asPercentages ? ccdf * 100 : ccdf); + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + } + return { + support, + data: ccdfData + }; + } + /* + Statistics snapshot of the query. + */ + /** Probability of doing strictly more than threshold damage (default >0). */ + probDamageGreaterThan(threshold = 0) { + let acc = 0; + for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; + return acc; + } + /** All outcome keys actually present (typed & ordered if you pass an order). */ + outcomeKeys(order) { + const found = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) + if (bin.count[k] && bin.count[k] > 0) found.add(k); + } + if (found.size === 0) + ["hit", "crit", "missNone"].forEach((k) => found.add(k)); + const keys = Array.from(found).filter( + (k) => order?.includes(k) ?? true + ); + if (order && order.length) + keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); + return keys; + } + /** Total probability per outcome across the PMF. */ + outcomeTotals(outcomes = this.outcomeKeys()) { + const totals = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => totals.set(o, 0)); + for (const [, row] of this.combined.map) { + for (const o of outcomes) { + const p = row.count[o] || 0; + totals.set(o, (totals.get(o) || 0) + p); + } + } + return totals; + } + /** Conditional damage range per outcome (min/avg/max of X | outcome). */ + outcomeDamageRanges(outcomes = this.outcomeKeys()) { + const table = this.toLabeledTable(outcomes); + const ranges = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); + for (const row of table) { + const dmg = row.damage; + for (const o of outcomes) { + const p = row[o] || 0; + if (p > 0) { + const r = ranges.get(o); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + } + const out = /* @__PURE__ */ new Map(); + for (const o of outcomes) { + const r = ranges.get(o); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); + } + return out; + } + /** + * Snapshot of the distribution in the exact shape the UI consumes. + * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) + * - damageRange is conditional on the outcome occurring + * + * The outcome probabilities use the correct Poisson-binomial marginals + * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), + * so they are always valid probabilities in [0,1]. + * + * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from + * the combined PMF's `count`, which the convolution accumulates as an EXPECTED + * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] + * rather than a clean conditional expectation. It is correct for a single + * attack. + */ + snapshot(order) { + const discovered = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) { + if (bin.count[k] && bin.count[k] > 0) discovered.add(k); + } + } + if (discovered.size === 0) { + for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); + } + let outcomes = Array.from(discovered); + if (order && order.length) { + const inOrder = new Set(order); + outcomes = outcomes.filter((k) => inOrder.has(k)); + const rank = new Map(order.map((k, i) => [k, i])); + outcomes.sort( + (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) + ); + } + const rows = this.toLabeledTable(outcomes); + const rangeAcc = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + rangeAcc.set(ot, { sum: 0, mass: 0 }); + } + for (const row of rows) { + const dmg = row.damage; + for (const ot of outcomes) { + const p = row[ot] || 0; + if (p <= 0) continue; + const r = rangeAcc.get(ot); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + const n = this.singles.length; + const outcomeMap = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + const r = rangeAcc.get(ot); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + outcomeMap.set(ot, { + atLeastOneProbability: this.probAtLeastOne(ot), + allProbability: this.probAtLeastK(ot, n), + damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } + }); + } + const averageDPR = this.mean(); + let damageChance = 0; + for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; + const { support, data } = this.toCDFSeries(false); + const quantile = (p) => { + if (support.length === 0) return 0; + for (let i = 0; i < support.length; i++) + if (data[i] >= p) return support[i]; + return support[support.length - 1]; + }; + const percentiles = { + p25: quantile(0.25), + p50: quantile(0.5), + p75: quantile(0.75) + }; + return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; + } + /** + * PMF Transformation Methods + * + * These methods provide a fluent API for transforming dice queries by wrapping + * the underlying PMF transformation methods. All operations work on the combined + * PMF and return new DiceQuery instances. + */ + /** + * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). + * + * @returns New DiceQuery with normalized combined PMF + */ + normalize() { + return new _DiceQuery([this.combined.normalize()]); + } + /** + * Returns a new DiceQuery with low-probability outcomes removed. + * + * @param eps Minimum probability threshold (defaults to PMF epsilon) + * @param keepFinalBin Whether to keep the highest damage bin regardless of probability + * @returns New DiceQuery with compacted combined PMF + */ + compact(eps, keepFinalBin) { + return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); + } + /** + * Returns a new DiceQuery with an additional scaled branch added. + * Useful for conditional outcomes like "30% chance of opportunity attack". + * + * @param branch DiceQuery to add as a scaled branch + * @param probability Probability of the branch occurring (0-1) + * @returns New DiceQuery combining this query with the scaled branch + * + * @example + * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); + * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); + */ + addScaled(branch, probability) { + return new _DiceQuery([ + this.combined.addScaled(branch.combined, probability) + ]); + } + /** + * Returns a new DiceQuery with all probabilities scaled by a factor. + * Used for conditional scenarios where the entire outcome has reduced probability. + * + * @param factor Scaling factor for probabilities + * @returns New DiceQuery with scaled probabilities + * + * @example + * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario + */ + scaleMass(factor) { + return new _DiceQuery([this.combined.scaleMass(factor)]); + } + totalMass() { + return this.combined.mass(); + } + /** + * Returns a new DiceQuery with damage values transformed by a function. + * Useful for applying modifiers, resistances, or other damage transformations. + * + * @param damageTransformFunction Function to transform each damage value + * @returns New DiceQuery with transformed damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage + * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage + */ + mapDamage(damageTransformFunction) { + return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); + } + /** + * Returns a new DiceQuery with damage values scaled by a factor. + * Convenient wrapper around mapDamage for multiplicative scaling. + * + * @param factor Scaling factor for damage values + * @param rounding Rounding method: "floor" (default), "round", or "ceil" + * @returns New DiceQuery with scaled damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const doubled = baseAttack.scaleDamage(2); // Double damage + * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded + */ + scaleDamage(factor, rounding = "floor") { + return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); + } + /** + * Returns a new DiceQuery combining this query with another via convolution. + * Equivalent to rolling both queries independently and adding results. + * It is important to use this rather than combing()ing the PMFs directly! + * This method maintains the provenance of the PMFs which is needed for damage attribution. + * Combining the .combined PMFs directly is still valid for DPR calculations but + * is not statistically sound for queries. + * + * @param other DiceQuery to combine with + * @param eps Optional epsilon for precision control + * @returns New DiceQuery representing the combined outcome + * + * @example + * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); + * const bothAttacks = mainAttack.convolve(bonusAttack); + */ + convolve(other) { + const singles = [...this.singles, ...other.singles]; + return new _DiceQuery(singles); + } + /** + * First-success split over an ordered list of DISTINCT single-swing PMFs. + * Each PMF may have different success/subset probabilities (from labels). + * + * successOutcome: e.g., ["success"] or ["hit", "crit"] + * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success + * + * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] + */ + firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { + const pmfs = this.singles; + if (!pmfs.length) { + throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); + } + const toArr = (x) => Array.isArray(x) ? x : [x]; + const clamp01 = (x) => Math.max(0, Math.min(1, x)); + const tol = Math.max(eps, 8 * Number.EPSILON); + const per = pmfs.map((pmf) => { + const dq = new _DiceQuery([pmf]); + const pS = dq.probAtLeastOne(toArr(successOutcome)); + const pB = dq.probAtLeastOne(toArr(subsetOutcome)); + if (pB - pS > eps) { + throw new Error( + "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." + ); + } + return { pS, pB }; + }); + let missSoFar = 1; + let pFirstSubset = 0; + let pFirstNonSubset = 0; + let pNone = 1; + for (const { pS, pB } of per) { + pFirstSubset += missSoFar * pB; + pFirstNonSubset += missSoFar * (pS - pB); + const miss = 1 - pS; + missSoFar *= miss; + pNone *= miss; + } + const pAny = 1 - pNone; + const a = clamp01(pFirstNonSubset); + const b = clamp01(pFirstSubset); + const any = clamp01(pAny); + const none = clamp01(pNone); + if (Math.abs(a + b - any) > tol * Math.max(1, any)) { + throw new Error( + `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` + ); + } + return [a, b, any, none]; + } +}; +_DiceQuery.DEFAULT_OUTCOMES = [ + "hit", + "crit", + "missNone" +]; +var DiceQuery = _DiceQuery; +var pmfCache = new LRUCache(1e3); +var _PMF = class _PMF { + constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { + this.map = map; + this.epsilon = epsilon; + this.normalized = normalized; + this.identifier = identifier; + this._preservedProvenance = _preservedProvenance; + } + static empty(epsilon = EPS, identifier = "empty") { + return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); + } + // This has a single bin at value 0, mass of 1 + static zero(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "zero"); + } + static delta(value, epsilon = EPS) { + return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); + } + /** + * Point mass at damage 0 tagged with the canonical `missNone` outcome. + * + * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the + * builder's attack-resolution vocabulary. This uses the `missNone` + * {@link OutcomeType} that the attribution charts and outcome stats key on, + * so it is the correct "clean miss / no damage" delta for provenance-aware + * mixtures feeding those consumers. + */ + static missNone(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "missNone"); + } + // This creates a single bin at value 0, but with weight 0. + static emptyMass() { + return _PMF.zero().scaleMass(0); + } + // Makes PMF iterable over [damage, bin] pairs. + [Symbol.iterator]() { + return this.map[Symbol.iterator](); + } + static clearCache() { + pmfCache.clear(); + } + /** + * Creates a conditional PMF from two branches (success and failure) and a probability. + * This is the core logic for modeling any probabilistic event where there are two + * distinct outcomes. + */ + static branch(successPMF, failurePMF, successProbability) { + let p = successProbability; + if (!Number.isFinite(p)) p = 0; + if (p < 0) p = 0; + if (p > 1) p = 1; + const q = 1 - p; + if (p === 0) return failurePMF.scaleMass(1); + if (p === 1) return successPMF.scaleMass(1); + const eps = successPMF.epsilon ?? failurePMF.epsilon; + const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; + const resultMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of failurePMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); + } + for (const [damageValue, bin] of successPMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); + } + return new _PMF(resultMap, eps, false, id); + } + /** + * withProbability() + * + * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). + * + * Think of this as a shortcut for: + * pmf.gate(p, PMF.zero()) + * + * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, + * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. + * + * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. + * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. + * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) + * use query.firstSuccessSplit() to get the exact probabilities. + * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). + * + */ + static withProbability(successPMF, probability) { + return _PMF.branch(successPMF, _PMF.zero(), probability); + } + /** + * gate() + * + * A conditional wrapper around branch() that applies this PMF with probability `p`, + * and applies a provided fallback PMF otherwise. + * + * This is useful for modeling a binary choice between two outcomes: + * - The "success" outcome (this PMF) happens with probability `p`. + * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. + * + * Examples: + * - 25% chance to include an opportunity attack, otherwise nothing: + * attackPMF.gate(0.25, PMF.zero()) + * + * - 50% chance to deal fireball damage, otherwise cone of cold damage: + * fireballPMF.gate(0.5, coneOfColdPMF) + * + * Relationship to other helpers: + * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. + * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). + * + * @param p Probability of applying this PMF (between 0 and 1). + * @param fallback PMF to apply when this PMF is *not* selected. + * @returns A new PMF representing the weighted mixture of this PMF and the fallback. + */ + gate(p, fallback) { + return _PMF.branch(this, fallback, p); + } + /** + * PMF.exclusive() + * + * Builds a single PMF from a set of mutually exclusive weighted outcomes. + * Exactly one of the provided options will occur. + * + * Each option has: + * - A PMF representing its outcome (e.g., damage dice). + * - A weight representing its probability of being selected. + * + * Notes: + * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for floating point rounding. + */ + static exclusive(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight) || weight < -eps) { + throw new Error(`PMF.exclusive: invalid weight ${weight}.`); + } + } + let totalWeight = items.reduce((s, { weight }) => s + weight, 0); + if (Math.abs(totalWeight) <= eps) totalWeight = 0; + if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; + if (totalWeight > 1 + EPS) { + throw new Error( + `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` + ); + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (weight > eps) out = out.addScaled(pmf, weight); + } + const leftover = Math.max(0, 1 - totalWeight); + if (leftover > eps) { + out = out.addScaled(_PMF.zero(), leftover); + } + return out; + } + /** + * PMF.mix() + * + * Builds a PMF as a linear combination of input PMFs with the given weights. + * Unlike `exclusive`, this does NOT: + * - enforce that weights sum to 1 + * - add leftover probability to δ0 (PMF.zero()) + * + * Use when outcomes are not mutually exclusive, or for interpolation/blending. + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for skipping tiny weights. + */ + static mix(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight)) { + throw new Error(`PMF.mix: invalid weight ${weight}.`); + } + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (Math.abs(weight) <= eps) continue; + out = out.addScaled(pmf, weight); + } + return out; + } + /** + * Adds damage attribution metadata to this PMF based on existing count metadata. + * For each bin, sets attr[outcome] = damage × count[outcome]. + * + * This enables damage attribution charts to work with builder-generated PMFs. + * The parser generates attr automatically, but builder PMFs only have count. + * + * @returns New PMF with attr field populated in each bin + */ + /** + * Returns true if this PMF already carries damage attribution metadata. + * + * Only the first positive-damage bin is inspected (parser-generated PMFs + * populate `attr` uniformly), so this is O(1) in practice. + */ + hasAttribution() { + for (const [damage, bin] of this.map) { + if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { + return true; + } + if (damage > 0) break; + } + return false; + } + withAttribution() { + if (this.hasAttribution()) return this; + const newMap = /* @__PURE__ */ new Map(); + for (const [damage, bin] of this.map) { + const attr = {}; + for (const outcome in bin.count) { + const probability = bin.count[outcome]; + if (probability > 0) { + attr[outcome] = damage * probability; + } + } + newMap.set(damage, { + p: bin.p, + count: { ...bin.count }, + attr: Object.keys(attr).length > 0 ? attr : void 0 + }); + } + return new _PMF( + newMap, + this.epsilon, + this.normalized, + `${this.identifier}~attr` + ); + } + /** + * General-purpose N-way mixture. + * weights: Array of [weight, PMF]. + * + * Example: PMF.mixN([ + * [pMiss, zero], + * [pHit, hitPMF], + * [pCrit, critPMF], + * ]); + */ + static mixN(weights, eps = EPS) { + const filtered = weights.filter(([w]) => w > eps); + if (filtered.length === 0) { + return _PMF.emptyMass(); + } + let acc = null; + let sum = 0; + for (const [w, pmf] of filtered) { + if (acc === null) { + acc = pmf; + sum = w; + } else { + const q = w / (sum + w); + acc = _PMF.branch(pmf, acc, q); + sum += w; + } + } + return acc ?? _PMF.emptyMass(); + } + // This is a convenience method for when we use power + // TODO: It can be smarter in the future, and we can also add it to query + // That way statistics operations on invalid PMFs can throw an error + // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? + setPreservedProvenance(preserved) { + if (!this._preservedProvenance && preserved) { + throw new Error( + "Preserved provenance is already set to false, cannot fix that" + ); + } + this._preservedProvenance = preserved; + } + preservedProvenance() { + return this._preservedProvenance; + } + getPowerCacheKey(n, eps) { + const id = this.identifier; + let key = `${id}`; + for (let i = 1; i < n; i++) key += `+${id}`; + return `${key}@${eps}`; + } + /** + * Efficiently computes this PMF convolved with itself `n` times. + * Uses exponentiation by squaring to reduce total convolutions. + * n must be a positive integer. + * * + * * NOTE: This folds multiple independent attacks into a single PMF. + * As a result, The power() method causes a loss of data provenance. + * This is ONLY SAFE if you are trying to calculate masses. + * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). + */ + power(n, eps = this.epsilon) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("power(n): n must be a positive integer"); + } + if (n === 1) return this; + const epsilon = eps ?? this.epsilon; + const key = this.getPowerCacheKey(n, epsilon); + { + const cached = pmfCache?.get(key); + if (cached) return cached; + } + let base = this.normalized ? this : this.normalize(); + let result = base; + let exp = n - 1; + while (exp > 0) { + if (exp & 1) { + result = result.convolve(base, epsilon); + } + exp >>= 1; + if (exp > 0) { + base = base.convolve(base, epsilon); + } + } + result.setPreservedProvenance(false); + { + pmfCache?.set(key, result); + } + return result; + } + /* + * Helper for chaining multiple identical attacks + */ + replicate(n) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("replicate(n): n must be a positive integer"); + } + if (n === 1) return [this]; + return Array.from({ length: n }, () => this); + } + mass() { + if (this._totalMass === void 0) { + let totalProbabilityMass = 0; + for (const { p } of this.map.values()) { + totalProbabilityMass += p; + } + this._totalMass = totalProbabilityMass; + } + return this._totalMass; + } + outcomeMass(outcome) { + let totalProbabilityMass = 0; + for (const { p, count } of this.map.values()) { + totalProbabilityMass += p * (count[outcome] ?? 0); + } + return totalProbabilityMass; + } + // Helper for testing + faceTotal() { + return [...this.map.keys()].reduce((sum, key) => sum + key, 0); + } + normalize() { + if (this.normalized) return this; + const normalizationFactor = this.mass(); + if (normalizationFactor === 0) return this; + const normalizedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const normalizedCount = {}; + for (const labelKey in probabilityBin.count) { + normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; + } + let normalizedAttributes; + if (probabilityBin.attr) { + normalizedAttributes = {}; + for (const labelKey in probabilityBin.attr) { + normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; + } + } + normalizedMap.set(damageValue, { + p: probabilityBin.p / normalizationFactor, + count: normalizedCount, + attr: normalizedAttributes + }); + } + return new _PMF(normalizedMap, this.epsilon, true, this.identifier); + } + /** + * Returns a copy with negligible probabilities removed (p < eps). + * If keepFinalBin is true, the bin with the largest key is always kept, + * even if its probability is below eps. count/attr submaps are still cleaned. + */ + compact(eps = this.epsilon, keepFinalBin = false) { + let maxKey = -Infinity; + if (keepFinalBin) { + for (const key of this.map.keys()) { + if (key > maxKey) maxKey = key; + } + } + const compactedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; + if (!shouldKeep) continue; + const cleanedBin = _PMF.cloneBin(probabilityBin); + for (const labelKey in cleanedBin.count) { + if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { + delete cleanedBin.count[labelKey]; + } + } + if (cleanedBin.attr) { + for (const labelKey in cleanedBin.attr) { + if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { + delete cleanedBin.attr[labelKey]; + } + } + if (Object.keys(cleanedBin.attr).length === 0) { + cleanedBin.attr = void 0; + } + } + compactedMap.set(damageValue, cleanedBin); + } + return new _PMF(compactedMap, eps, this.normalized, this.identifier); + } + // Note: The "support" of a PMF is the set of all non-zero probability outcomes. + // This returns all damage values with non-zero probability, sorted ascending. + support() { + if (this._support === void 0) { + this._support = [...this.map.keys()].sort((a, b) => a - b); + } + return this._support; + } + // Minimum possible damage value. + min() { + if (this._min === void 0) { + const support = this.support(); + this._min = support.length > 0 ? support[0] : 0; + } + return this._min; + } + // Maximum possible damage value. + max() { + if (this._max === void 0) { + const support = this.support(); + this._max = support.length > 0 ? support[support.length - 1] : 0; + } + return this._max; + } + /** + * Returns the expected (mean) damage value. + * Cached for performance since this requires iterating through all bins. + */ + mean() { + if (this._mean === void 0) { + let totalSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + totalSum += damageValue * probabilityBin.p; + } + this._mean = totalSum; + } + return this._mean; + } + /** + * Returns the variance of the damage distribution. + * Cached for performance since this requires mean calculation plus iteration. + */ + variance() { + if (this._variance === void 0) { + const meanValue = this.mean(); + let varianceSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + const deviationFromMean = damageValue - meanValue; + varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; + } + this._variance = varianceSum; + } + return this._variance; + } + /** + * Returns the standard deviation of the damage distribution. + */ + stdev() { + if (this._stdev === void 0) { + this._stdev = Math.sqrt(this.variance()); + } + return this._stdev; + } + /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ + static cloneBin(bin) { + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ + static scaleBin(bin, factor) { + const count = {}; + for (const k in bin.count) { + count[k] = bin.count[k] * factor; + } + let attr; + if (bin.attr) { + attr = {}; + for (const k in bin.attr) { + attr[k] = bin.attr[k] * factor; + } + } + return { p: bin.p * factor, count, attr }; + } + static mergeInto(destinationMap, damageValue, binToAdd) { + const existingBin = destinationMap.get(damageValue); + if (!existingBin) { + destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); + return; + } + existingBin.p += binToAdd.p; + for (const labelKey in binToAdd.count) { + existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; + } + if (binToAdd.attr) { + if (!existingBin.attr) { + existingBin.attr = {}; + } + for (const labelKey in binToAdd.attr) { + existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; + } + } + } + // Convenience method + add(other) { + return this.addScaled(other, 1); + } + /** + * Returns a new PMF with a scaled branch added to this one. + * The branch PMF is scaled by the given probability before merging + * This will be very useful for conditional effects and for being + * able to model "I can probably have this opportunity attack 40% of rounds" + * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes + */ + addScaled(branch, probability) { + if (probability === 0) return this; + const resultMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of this.map) { + resultMap.set(dmg, _PMF.cloneBin(bin)); + } + for (const [damageValue, probabilityBin] of branch.map) { + _PMF.mergeInto( + resultMap, + damageValue, + _PMF.scaleBin(probabilityBin, probability) + ); + } + return new _PMF( + resultMap, + this.epsilon, + false, + `${this.identifier}+scaled(${branch.identifier},${probability})` + ); + } + /** + * Redistributes probability mass to model an effect that only occurs with + * probability `frequency` — a conditional attack, an on-hit rider, or a + * sub-one AoE target fraction. + * + * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, + * per-label `count`, AND per-label `attr` — and the freed mass is moved into + * the miss bin at damage 0, tagged with the canonical `missNone` outcome. + * Total probability mass is preserved. + * + * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage + * attribution (`attr`) intact, so a frequency-scaled PMF still renders + * correctly in the damage-attribution charts. + * + * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` + * collapses all mass into the miss bin. The miss outcome is assumed to be + * encoded at damage value 0. + * + * @param frequency Probability in [0, 1] that the effect occurs. + */ + applyHitFrequency(frequency) { + if (!Number.isFinite(frequency) || frequency >= 1) return this; + const freq = Math.max(0, frequency); + const pMiss = this.pAt(0); + const pHit = 1 - pMiss; + const newMissMass = pMiss + (1 - freq) * pHit; + const newMap = /* @__PURE__ */ new Map(); + newMap.set(0, { + p: newMissMass, + count: { [MISS_NONE_OUTCOME]: newMissMass }, + attr: {} + }); + for (const [damage, bin] of this.map) { + if (damage <= 0) continue; + newMap.set(damage, _PMF.scaleBin(bin, freq)); + } + return new _PMF( + newMap, + this.epsilon, + false, + `freq(${this.identifier},${freq})` + ); + } + scaleMass(factor) { + if (factor === 1) return this; + const scaledMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); + } + return new _PMF( + scaledMap, + this.epsilon, + false, + `scale(${this.identifier},${factor})` + ); + } + mapDamage(damageTransformFunction) { + const transformedMap = /* @__PURE__ */ new Map(); + for (const [originalDamage, probabilityBin] of this.map) { + const transformedDamage = damageTransformFunction(originalDamage); + _PMF.mergeInto( + transformedMap, + transformedDamage, + _PMF.cloneBin(probabilityBin) + ); + } + return new _PMF( + transformedMap, + this.epsilon, + this.normalized, + `map(${this.identifier})` + ); + } + scaleDamage(factor, rounding = "floor") { + const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; + return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); + } + getPMFCombineCacheKey(p1, p2, eps, raw) { + const [id1, id2] = [p1.identifier, p2.identifier].sort(); + return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; + } + /** + * A small content fingerprint (mass + bin count + face sum) so convolution + * cache keys change if the underlying numbers do. Memoized because a PMF is + * immutable once constructed — this avoids re-summing every key on each + * convolve() call (including cache hits). + */ + fingerprint() { + if (this._fingerprint === void 0) { + let faceSum = 0; + for (const k of this.map.keys()) faceSum += k; + this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; + } + return this._fingerprint; + } + convolve(other, eps, raw = false) { + const epsilon = eps ?? this.epsilon; + const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); + const A0 = norm(this); + const B0 = norm(other); + const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; + const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); + const cached = pmfCache?.get(cacheKey); + if (cached) return cached; + const combinedMap = /* @__PURE__ */ new Map(); + for (const [aVal, aBin] of A.map) { + const ap = aBin.p; + const aCount = aBin.count; + const aAttr = aBin.attr; + for (const [bVal, bBin] of B.map) { + const bp = bBin.p; + const dmg = aVal + bVal; + let dest = combinedMap.get(dmg); + if (dest === void 0) { + dest = { p: 0, count: {} }; + combinedMap.set(dmg, dest); + } + dest.p += ap * bp; + const dc = dest.count; + for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; + for (const k in bBin.count) + dc[k] = (dc[k] || 0) + bBin.count[k] * ap; + if (aAttr || bBin.attr) { + let da = dest.attr; + if (da === void 0) { + da = {}; + dest.attr = da; + } + if (aAttr) + for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; + if (bBin.attr) + for (const k in bBin.attr) + da[k] = (da[k] || 0) + bBin.attr[k] * ap; + } + } + } + let result = new _PMF( + combinedMap, + epsilon, + !raw, + `${A.identifier}${raw ? "*" : "+"}${B.identifier}` + ); + const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); + const mGot = result.mass(); + if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { + result = result.scaleMass(mExp / mGot); + } + if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) + result = result.normalize(); + pmfCache?.set(cacheKey, result); + return result; + } + // 3) Nice wrapper so you can call pmf.combineRaw(other) + combineRaw(other, eps) { + return this.convolve(other, eps, true); + } + // Reduce a list of PMFs by left-folding convolve() with the given eps + static reduceConvolveLeft(pmfList, eps) { + let result = pmfList[0]; + for (let i = 1; i < pmfList.length; i++) { + result = result.convolve(pmfList[i], eps); + } + return result; + } + /** + * Convolves multiple PMFs using linear convolution with automatic caching. + * Uses a left-to-right accumulation approach for maximum cache reuse. + * Each convolve() call automatically uses the convolution cache for performance. + * + * This linear approach provides better cache hits than pairwise because: + * - Intermediate results are more predictable and stable + * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) + * - Order-independent cache keys work better with consistent build patterns + */ + static convolveMany(pmfList, eps = EPS) { + if (pmfList.length === 0) return _PMF.empty(eps); + if (pmfList.length === 1) return pmfList[0]; + return _PMF.reduceConvolveLeft(pmfList, eps); + } + /** + * Returns a plain, JSON-serializable representation of this PMF. + * + * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces + * the expected output (no double-encoding). Use {@link PMF.fromJSON} to + * reconstruct, or {@link PMF.toJSONString} if you need the string directly. + */ + toJSON() { + return { + bins: [...this.map.entries()], + normalized: this.normalized, + identifier: this.identifier + }; + } + /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ + toJSONString() { + return JSON.stringify(this); + } + static fromJSON(jsonData) { + return new _PMF( + new Map(jsonData.bins), + EPS, + !!jsonData.normalized, + jsonData.identifier || "fromJSON" + ); + } + /** + * Relative pruning with optional top-K floor. + * Keeps bins with p >= epsRel * peak, always keeps min and max damage, + * optionally guarantees at least `minBins` survivors by adding top-K. + * Returns a new, non-normalized PMF. + */ + prune(epsRel, minBins = 0) { + const size = this.map.size; + if (size === 0) return this; + let peak = 0; + let minDamage = Number.POSITIVE_INFINITY; + let maxDamage = Number.NEGATIVE_INFINITY; + for (const [dmg, bin] of this.map) { + if (bin.p > peak) peak = bin.p; + if (dmg < minDamage) minDamage = dmg; + if (dmg > maxDamage) maxDamage = dmg; + } + if (peak === 0) + return new _PMF(new Map(this.map), epsRel, false, this.identifier); + const thresh = epsRel * peak; + const entries = [...this.map.entries()]; + const survivorsByDmg = /* @__PURE__ */ new Map(); + const protect = (d2) => { + const b = this.map.get(d2); + if (b) survivorsByDmg.set(d2, b); + }; + protect(minDamage); + if (maxDamage !== minDamage) protect(maxDamage); + for (const [dmg, bin] of entries) { + if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); + } + if (minBins > 0 && survivorsByDmg.size < minBins) { + entries.sort((a, b) => b[1].p - a[1].p); + for (const [dmg, bin] of entries) { + if (!survivorsByDmg.has(dmg)) { + survivorsByDmg.set(dmg, bin); + if (survivorsByDmg.size >= minBins) break; + } + } + } + const prunedMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of survivorsByDmg) { + const newCount = {}; + for (const k in bin.count) { + const v = bin.count[k]; + if (Math.abs(v) >= thresh) newCount[k] = v; + } + let newAttr; + if (bin.attr) { + for (const k in bin.attr) { + const v = bin.attr[k]; + if (Math.abs(v) >= thresh) { + if (!newAttr) newAttr = {}; + newAttr[k] = v; + } + } + } + prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); + } + return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); + } + /** Probability mass at exactly x. */ + pAt(x) { + return this.map.get(x)?.p ?? 0; + } + /** + * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. + * Assumes a miss is encoded as the damage-0 bin (the convention used across + * attack/save PMFs). The dual of {@link missProbability}. + */ + hitProbability() { + return 1 - this.pAt(0); + } + /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ + missProbability() { + return this.pAt(0); + } + /** + * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width + * damage buckets, aggregating probability mass (and `count`/`attr` + * provenance) into each bucket's start value. Returns this PMF unchanged when + * its integer support already fits within `maxBuckets`. + * + * This is a lossy display/downsampling transform (bucket start replaces the + * exact damage value) — use it for charting wide distributions, not for DPR + * math. + */ + rebin(maxBuckets) { + if (!(maxBuckets > 0)) return this; + const support = this.support(); + if (support.length === 0) return this; + const min = support[0]; + const max = support[support.length - 1]; + const range = max - min; + if (range + 1 <= maxBuckets) return this; + const binSize = Math.ceil((range + 1) / maxBuckets); + return this.mapDamage((d2) => min + Math.floor((d2 - min) / binSize) * binSize); + } + /** Dense integer support from min..max (inclusive). + * Useful for showing empty bars in charts. + */ + denseSupport() { + const s = this.support(); + if (s.length === 0) return []; + const lo = Math.min(...s), hi = Math.max(...s); + return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( + (a, b) => a - b + ); + } + /** CDF at x: P(X ≤ x). */ + cdfAt(x) { + let acc = 0; + for (const [val, bin] of this.map) if (val <= x) acc += bin.p; + return acc; + } + /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ + quantile(p) { + if (this.map.size === 0) return 0; + const s = this.support().sort((a, b) => a - b); + let acc = 0; + for (const x of s) { + acc += this.pAt(x); + if (acc >= p) return x; + } + return s[s.length - 1]; + } + /** Get outcome probability at specific damage value. */ + outcomeAt(damage, outcome) { + return this.map.get(damage)?.count[outcome] ?? 0; + } + /** Get all outcome types present in this PMF. */ + outcomes() { + const outcomeSet = /* @__PURE__ */ new Set(); + for (const [, bin] of this.map) { + for (const outcome in bin.count) { + if (bin.count[outcome] > 0) { + outcomeSet.add(outcome); + } + } + } + return Array.from(outcomeSet).sort(); + } + /** Get total probability of an outcome across all damage values. */ + outcomeProbability(outcome) { + let total = 0; + for (const [, bin] of this.map) { + total += bin.count[outcome] ?? 0; + } + return total; + } + /** Get damage attribution for an outcome at specific damage value. */ + outcomeAttributionAt(damage, outcome) { + return this.map.get(damage)?.attr?.[outcome] ?? 0; + } + /** Get all outcome data at specific damage value. */ + binAt(damage) { + const bin = this.map.get(damage); + if (!bin) return null; + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Check if outcome exists in this PMF. */ + hasOutcome(outcome) { + for (const [, bin] of this.map) { + if ((bin.count[outcome] ?? 0) > 0) { + return true; + } + } + return false; + } + /** + * Split each damage value's probability mass across outcome labels, returning + * per-label maps of `damage value → probability mass attributable to that + * label`. Summing over labels at a given value recovers that value's `p`. + * + * Damage-bearing bins are split by `attr` weight (the share of damage each + * outcome contributed); the clean-miss bin at 0 is split by `count` weight + * (there is no damage to attribute). Attribution is computed on demand via + * {@link withAttribution} when absent, so builder-generated PMFs work too. + * + * This is the provenance core of the stacked damage-attribution chart — the + * caller only maps these series into its rendering format (colors, binning, + * axis labels). + */ + attributionByValue() { + const src = this.hasAttribution() ? this : this.withAttribution(); + const result = /* @__PURE__ */ new Map(); + const add = (label, damage, mass) => { + if (!(mass > 0)) return; + let series = result.get(label); + if (!series) { + series = /* @__PURE__ */ new Map(); + result.set(label, series); + } + series.set(damage, (series.get(damage) ?? 0) + mass); + }; + for (const [damage, bin] of src.map) { + const p = bin.p || 0; + if (p <= 0) continue; + const isMissBin = damage === 0; + if (isMissBin) { + let totalCount = 0; + for (const k in bin.count) totalCount += bin.count[k] || 0; + if (totalCount > 0) { + const c = bin.count[MISS_NONE_OUTCOME] || 0; + add(MISS_NONE_OUTCOME, damage, c / totalCount * p); + } + continue; + } + let totalAttr = 0; + if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; + if (bin.attr && totalAttr > 0) { + for (const k in bin.attr) { + if (k === MISS_NONE_OUTCOME) continue; + add(k, damage, (bin.attr[k] || 0) / totalAttr * p); + } + } + } + return result; + } + tailProbGE(t) { + let s = 0; + for (const [x, bin] of this) { + if (bin.p > 0 && x >= t) s += bin.p; + } + return s; + } + tailProbGT(t) { + let s = 0; + for (const [x, rec] of this) { + if (x > t) s += rec.p; + } + return s; + } + /** + * Returns a new PMF containing only bins where the specified outcome has non-zero probability. + * This creates a marginal distribution for the given outcome type, with probabilities + * scaled to represent the unconditional mass attributable to that outcome. + */ + filterOutcome(outcome) { + const filteredMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of this.map) { + const outcomeCount = bin.count[outcome] ?? 0; + const totalCount = Object.values(bin.count ?? {}).reduce( + (a, b) => (a ?? 0) + (b ?? 0), + 0 + ); + if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { + const proportion = outcomeCount / totalCount; + const newP = bin.p * proportion; + const newCount = { [outcome]: outcomeCount }; + let newAttr; + if (bin.attr && bin.attr[outcome] !== void 0) { + newAttr = { [outcome]: bin.attr[outcome] * proportion }; + } + filteredMap.set(damageValue, { + p: newP, + count: newCount, + attr: newAttr + }); + } + } + return new _PMF( + filteredMap, + this.epsilon, + false, + // don't normalize by default + `filter(${this.identifier},${outcome})` + ); + } + /** + * Calculates probabilities for first-success outcomes across n independent attempts. + * + * @param pSuccess - Total probability of any success on a single attempt. + * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). + * @param n - Number of independent attempts. + * + * Returns: + * - pSpecificSuccess: Probability that the first success was of the "special" type + * - pGeneralSuccess: Probability that the first success was of the non-special type + * - pNone: Probability that no successes occurred + * - pAny: Probability that at least one success occurred + */ + static firstSuccessWeights(pSuccess, pSpecial, n) { + if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { + throw new Error( + `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` + ); + } + const pFail = 1 - pSuccess; + const pFailAll = Math.pow(pFail, n); + const pAny = 1 - pFailAll; + const denom = pSuccess === 0 ? 1 : pSuccess; + const pSpecificSuccess = pSpecial * pAny / denom; + const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; + const pNone = 1 - pSpecificSuccess - pGeneralSuccess; + return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; + } + mapValues(f, eps = EPS, opts) { + const rounding = opts?.rounding ?? "none"; + const preserveCounts = opts?.preserveCounts ?? true; + const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; + const probs = /* @__PURE__ */ new Map(); + const counts = /* @__PURE__ */ new Map(); + for (const [v, bin] of this) { + if (Math.abs(bin.p) < eps) continue; + const u = round(f(v)); + probs.set(u, (probs.get(u) ?? 0) + bin.p); + if (preserveCounts) { + const src = bin.count; + if (src) { + const dest = counts.get(u) ?? {}; + for (const k in src) { + dest[k] = (dest[k] ?? 0) + src[k]; + } + counts.set(u, dest); + } + } + } + const internal = /* @__PURE__ */ new Map(); + for (const [u, p] of probs) { + internal.set(u, { p, count: counts.get(u) ?? {} }); + } + return _PMF.fromMap( + new Map(Array.from(internal, ([u, b]) => [u, b.p])), + eps + ); + } + static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { + const filtered = []; + for (const [v, p] of m) { + if (!Number.isFinite(v) || !Number.isFinite(p)) continue; + if (p <= 0 || Math.abs(p) < eps) continue; + if (requireIntegerValues && !Number.isInteger(v)) { + throw new Error(`fromMap: non-integer outcome ${v}`); + } + filtered.push([v, p]); + } + if (filtered.length === 0) { + throw new Error("fromMap: empty or invalid input map"); + } + let sum = 0; + let c = 0; + for (const [, p] of filtered) { + const y = p - c; + const t = sum + y; + c = t - sum - y; + sum = t; + } + if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); + filtered.sort((a, b) => a[0] - b[0]); + const internal = /* @__PURE__ */ new Map(); + for (const [v, p] of filtered) { + internal.set(v, { p: p / sum, count: {} }); + } + return new _PMF(internal, eps); + } + query() { + return new DiceQuery(this); + } +}; +// Unique ID generator for anonymous PMFs to avoid cache key collisions +_PMF.__anonIdCounter = 1; +var PMF = _PMF; + +// src/pmf/mixture.ts +var Mixture = class _Mixture { + constructor(eps = EPS) { + this.totals = /* @__PURE__ */ new Map(); + // raw mass per outcome (pre-normalization) + this.labelMass = /* @__PURE__ */ new Map(); + this.eps = Number.isFinite(eps) ? eps : EPS; + } + /** Remove all accumulated state. */ + clear() { + this.totals.clear(); + this.labelMass.clear(); + return this; + } + /** Number of distinct outcome values currently accumulated. */ + size() { + return this.totals.size; + } + /** Whether a label was ever added. */ + hasLabel(label) { + for (const bag of this.labelMass.values()) if (bag[label]) return true; + return false; + } + /** + * Add a labeled component with a mixture weight. + * Weight can be any positive finite number. Very small contributions are pruned by eps. + */ + add(label, pmf, weight = 1) { + if (!Number.isFinite(weight) || weight <= 0) return this; + for (const [v, bin] of pmf) { + const p = bin.p; + if (p <= 0) continue; + const add = weight * p; + if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; + this.totals.set(v, (this.totals.get(v) ?? 0) + add); + const bag = this.labelMass.get(v) ?? {}; + bag[label] = (bag[label] ?? 0) + add; + this.labelMass.set(v, bag); + } + return this; + } + buildPMF(eps = EPS) { + let grand = 0; + let c = 0; + for (const m of this.totals.values()) { + const y = m - c; + const t = grand + y; + c = t - grand - y; + grand = t; + } + if (!(grand > 0)) throw new Error("Mixture: zero total mass"); + const internal = /* @__PURE__ */ new Map(); + for (const [v, m] of this.totals) { + if (m <= 0 || Math.abs(m) < this.eps) continue; + const count = this.labelMass.get(v) ?? {}; + internal.set(v, { p: m / grand, count }); + } + return new PMF(internal, eps); + } + /** + * Produce normalized *per-label* PMFs (labels independent). + * These are unlabeled PMFs built from the raw mass of that label alone. + */ + byOutcome() { + const labels = /* @__PURE__ */ new Set(); + for (const bag of this.labelMass.values()) { + for (const k of Object.keys(bag)) labels.add(k); + } + const out = {}; + for (const label of labels) { + const m = /* @__PURE__ */ new Map(); + for (const [v, bag] of this.labelMass) { + const w = bag[label]; + if (w && Math.abs(w) >= this.eps) m.set(v, w); + } + if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); + } + return out; + } + /** + * Mixture weights per label, normalized to sum to 1 over labels that appeared. + * Uses raw mass before per-outcome normalization. + */ + weights() { + const res = {}; + for (const [, bag] of this.labelMass) { + for (const [lab, w] of Object.entries(bag)) { + if (!Number.isFinite(w) || w <= 0) continue; + res[lab] = (res[lab] ?? 0) + w; + } + } + let total = 0; + let c = 0; + for (const v of Object.values(res)) { + const y = v - c; + const t = total + y; + c = t - total - y; + total = t; + } + if (total > 0) { + for (const k in res) res[k] = res[k] / total; + } + return res; + } + toJSON() { + return { + totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), + labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), + eps: this.eps + }; + } + static mix(items, eps = EPS) { + const mix = new _Mixture(eps); + for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); + return mix.buildPMF(); + } +}; + +// src/common/errors.ts +var DiceParseError = class _DiceParseError extends Error { + constructor(message, options) { + super(message); + this.name = "DiceParseError"; + this.expression = options?.expression; + this.cause = options?.cause; + Object.setPrototypeOf(this, _DiceParseError.prototype); + } +}; + +// src/parser/dice.ts +var MAX_BINARY_OUTCOMES = 1e8; +var Dice = class _Dice { + constructor(x = 0) { + this.faces = {}; + this.privateData = {}; + // Partial: the object starts empty and gains keys as outcomes are recorded, + // so the type must not claim every OutcomeType is present. (Previously typed + // as a full Record via an `as` cast, which lied about missing keys.) + this.outcomeData = {}; + this.hasHitDistributionCalculated = false; + if (x <= 0) return; + for (let i = 1; i <= x; i++) { + this.faces[i] = 1; + } + } + getOutcomeDistribution(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const distribution = this.outcomeData[key]; + if (distribution === void 0) return void 0; + return { ...distribution }; + } + getFullOutcomeDistribution() { + return { ...this.outcomeData }; + } + setOutcomeDistribution(key, data) { + if (data) { + this.outcomeData[key] = data; + } else { + delete this.outcomeData[key]; + } + } + hasOutcomeData(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const data = this.outcomeData[key]; + return data !== void 0 && Object.keys(data).length > 0; + } + getOutcomeCount(key, face) { + return this.outcomeData[key]?.[face] ?? 0; + } + getAverage(key) { + const distribution = this.getOutcomeDistribution(key); + if (!distribution) return 0; + const totalCount = Object.values(distribution).reduce( + (sum, count) => sum + count, + 0 + ); + const expectedDamage = Object.entries(distribution).reduce( + (sum, [damage, count]) => sum + Number(damage) * count, + 0 + ); + if (totalCount === 0) return 0; + return expectedDamage / totalCount; + } + // TODO this can be private later if we change how testing works + calculateHitDistribution() { + const hitValues = {}; + const subtractedOutcomes = [ + this.outcomeData.crit, + this.outcomeData.missNone, + this.outcomeData.missDamage, + this.outcomeData.saveHalf, + this.outcomeData.saveFail, + this.outcomeData.pc + ]; + for (const [face, totalCount] of Object.entries(this.faces)) { + const numFace = Number(face); + let hitCount = totalCount; + for (const distribution of subtractedOutcomes) { + const outcomeCount = distribution?.[numFace]; + if (outcomeCount) { + hitCount -= outcomeCount; + } + } + if (numFace === 0) { + hitCount = 0; + } + if (hitCount < 0) { + hitCount = 0; + } + hitValues[numFace] = hitCount; + } + return hitValues; + } + ensureHitDistribution() { + if (!this.hasHitDistributionCalculated) { + const hitValues = this.calculateHitDistribution(); + this.setOutcomeDistribution("hit", hitValues); + this.hasHitDistributionCalculated = true; + } + } + // PRIVATE FUNCTIONS + binaryOp(other, op, diceConstructor) { + const result = diceConstructor ? diceConstructor() : new _Dice(); + const isScalar = typeof other === "number"; + const keys1 = this.keys(); + const keys2 = isScalar ? [] : other.keys(); + if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { + throw new DiceParseError( + `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` + ); + } + for (const key1 of keys1) { + const value1 = this.faces[key1]; + if (isScalar) { + const resultKey = op(key1, other); + result.increment(resultKey, value1); + } else { + for (const key2 of keys2) { + const value2 = other.faces[key2]; + const resultKey = op(key1, key2); + result.increment(resultKey, value1 * value2); + } + } + } + return result; + } + removeFaces(facesToRemove) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (!facesToRemove.includes(numKey)) { + result.faces[numKey] = value; + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // PUBLIC FUNCTIONS + getFaceEntries() { + return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); + } + getFaceMap() { + return { ...this.faces }; + } + get(face) { + return this.faces[face] ?? 0; + } + keys() { + return Object.keys(this.faces).map(Number); + } + values() { + return Object.values(this.faces); + } + total() { + return Object.values(this.faces).reduce((sum, value) => sum + value, 0); + } + setFace(key, value) { + this.faces[key] = value; + } + static scalar(value) { + const result = new _Dice(); + result.increment(value, 1); + return result; + } + maxFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.max(...numericKeys); + } + minFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.min(...numericKeys); + } + increment(face, count) { + const current = this.faces[face] || 0; + this.faces[face] = current + count; + } + normalize(scalar) { + const result = new _Dice(); + for (const [face, count] of Object.entries(this.faces)) { + result.faces[Number(face)] = count * scalar; + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // OPERATIONS + add(other) { + return this.binaryOp(other, (a, b) => a + b); + } + subtract(other) { + return this.binaryOp(other, (a, b) => a - b); + } + conditionalApply(other) { + return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); + } + multiply(other) { + return this.binaryOp(other, (a, b) => a * b); + } + addNonZero(other) { + return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); + } + eq(other) { + return this.binaryOp(other, (a, b) => a === b ? 1 : 0); + } + max(other) { + return this.binaryOp(other, (a, b) => Math.max(a, b)); + } + min(other) { + return this.binaryOp(other, (a, b) => Math.min(a, b)); + } + advantage() { + return this.max(this); + } + ge(other) { + return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); + } + divide(other) { + return this.binaryOp(other, (a, b) => a / b); + } + divideRoundUp(other) { + return this.binaryOp(other, (a, b) => Math.ceil(a / b)); + } + divideRoundDown(other) { + return this.binaryOp(other, (a, b) => Math.floor(a / b)); + } + and(other) { + return this.binaryOp(other, (a, b) => a && b ? 1 : 0); + } + checkTarget(other, comparisonLogic) { + const createResult = () => { + const result = new _Dice(); + result.increment(0, 0); + result.increment(1, 0); + return result; + }; + return this.binaryOp(other, comparisonLogic, createResult); + } + dc(other) { + const dcCheck = (a, b) => a >= b ? 0 : 1; + const result = this.checkTarget(other, dcCheck); + result.privateData.isDCCheck = true; + return result; + } + ac(other) { + const acCheck = (a, b) => a >= b ? a : 0; + return this.checkTarget(other, acCheck); + } + deleteFace(face) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (numKey !== face) { + result.increment(numKey, value); + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + reroll(toReroll) { + const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; + const rerollKeys = rerollDice.keys(); + const rerollSet = new Set(rerollKeys); + const removed = this.removeFaces(rerollKeys); + let result = new _Dice(); + for (const face of this.keys()) { + const wasRerolled = rerollSet.has(face); + result = result.combine(removed); + if (wasRerolled) { + result = result.combine(this); + } + } + return result; + } + // This is not addition and not rolling two dice at once. + // Instead, it’s mixing two distributions into a single weighted die. + combine(other) { + if (typeof other === "number") { + other = _Dice.scalar(other); + } + const result = new _Dice(); + for (const [key, value] of Object.entries(other.faces)) { + result.faces[Number(key)] = value; + } + const except = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + result.increment(numKey, value); + if (!(numKey in other.faces)) { + except.increment(numKey, value); + } + } + result.privateData = { ...this.privateData, except: other }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + combineInPlace(other) { + for (const [key, value] of Object.entries(other.faces)) { + const numKey = Number(key); + const current = this.faces[numKey] || 0; + this.faces[numKey] = current + value; + } + } + percent() { + const total = this.total(); + const result = {}; + for (const [face, count] of Object.entries(this.faces)) { + result[Number(face)] = count / total; + } + return result; + } + average() { + const total = this.total(); + if (total === 0) return 0; + let sum = 0; + for (const [key, value] of Object.entries(this.faces)) { + sum += Number(key) * value; + } + return sum / total; + } + /* + * Convert dice to PMF using OutcomeType labels directly from damage distribution. + * This is much cleaner than the original complex distribution conversion. + */ + toPMF(numEpsilon = EPS) { + const total = this.total(); + if (total === 0) return PMF.empty(numEpsilon); + this.ensureHitDistribution(); + const map = /* @__PURE__ */ new Map(); + const hitDistro = this.getOutcomeDistribution("hit") || {}; + const critDistro = this.getOutcomeDistribution("crit") || {}; + const missDistro = this.getOutcomeDistribution("missDamage") || {}; + const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; + const pcDistro = this.getOutcomeDistribution("pc") || {}; + const isSaveHalf = Object.keys(saveDistro).length > 0; + const isDCCheck = this.privateData.isDCCheck === true; + const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; + for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { + const face = Number(faceStr); + const faceCount = Number(faceCountRaw); + if (faceCount <= 0) continue; + let p = faceCount / total; + p = clampNonNeg(p); + if (!(p > 0)) continue; + if (numEpsilon >= 0 && p < numEpsilon) continue; + const count = {}; + const attr = {}; + if (hitDistro[face]) { + const c = clampNonNeg(hitDistro[face] / total); + if (c > 0) { + if (isSaveHalf || isDCCheck) { + count.saveFail = c; + attr.saveFail = clampNonNeg(face * hitDistro[face] / total); + } else { + count.hit = c; + attr.hit = clampNonNeg(face * hitDistro[face] / total); + } + } + } + if (critDistro[face]) { + const c = clampNonNeg(critDistro[face] / total); + if (c > 0) { + count.crit = c; + attr.crit = clampNonNeg(face * critDistro[face] / total); + } + } + if (missDistro[face]) { + const c = clampNonNeg(missDistro[face] / total); + if (c > 0) { + count.missDamage = c; + attr.missDamage = clampNonNeg(face * missDistro[face] / total); + } + } + if (saveDistro[face]) { + const c = clampNonNeg(saveDistro[face] / total); + if (c > 0) { + if (isSaveHalf) { + count.saveHalf = c; + attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); + } else { + count.saveFail = (count.saveFail ?? 0) + c; + attr.saveFail = clampNonNeg( + (attr.saveFail ?? 0) + face * saveDistro[face] / total + ); + } + } + } + if (pcDistro[face]) { + const c = clampNonNeg(pcDistro[face] / total); + if (c > 0) { + count.pc = c; + attr.pc = clampNonNeg(face * pcDistro[face] / total); + } + } + if (!isSaveHalf && !isDCCheck) { + const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); + const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); + if (unaccountedCount > 0) { + const frac = clampNonNeg(unaccountedCount / total); + if (frac > 0) { + count.missNone = (count.missNone ?? 0) + frac; + } + } + } + const bin = { p, count }; + if (Object.keys(attr).length > 0) { + bin.attr = attr; + } + map.set(face, bin); + } + const identifier = this.identifier || "ERROR"; + return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); + } +}; + +// src/parser/parser.ts +var MAX_DIE_SIDES = 1e6; +var MAX_DICE_COUNT = 1e4; +var MAX_KEEP_OUTCOMES = 1e6; +var parseCache = new LRUCache(1e3); +function parse(expression, n = 0) { + const cleaned = expression.replace(/ /g, "").toLowerCase(); + { + const cacheKey = `${cleaned}:${n}`; + const cached = parseCache.get(cacheKey); + if (cached) return cached; + } + const chars = [...cleaned]; + let result; + try { + result = parseExpression(chars, n); + } catch (error) { + throw new DiceParseError( + `Cannot parse dice expression [${expression}]: ${error}`, + { expression, cause: error } + ); + } + result.privateData = result.privateData || {}; + result.identifier = cleaned; + if (chars.length > 0) { + throw new DiceParseError( + `Unexpected token: '${chars[0]}' from expression: '${expression}'`, + { expression } + ); + } + const resultPMF = result.toPMF(-1); + { + const cacheKey = `${cleaned}:${n}`; + parseCache.set(cacheKey, resultPMF); + } + return resultPMF; +} +function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { + dice = dice.normalize(currentNorm); + finalResult = finalResult.normalize(normValue); + finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); + finalResult = finalResult.combine(dice); + return { newNorm: currentNorm * normValue, updatedResult: finalResult }; +} +function parseExpression(arr, n) { + const result = (() => { + const res = parseArgument(arr, n); + return typeof res === "number" ? Dice.scalar(res) : res; + })(); + let op = parseOperation(arr); + let finalResult = result; + while (op != null) { + const arg = !op.unary ? parseArgument(arr, n) : finalResult; + let crit; + let critNorm = 1; + if (arr[0] === "x" || arr[0] === "c") { + const isXcrit = arr[0] === "x"; + if (isXcrit) assertToken(arr, "x"); + assertToken(arr, "c"); + assertToken(arr, "r"); + assertToken(arr, "i"); + assertToken(arr, "t"); + const count = isXcrit ? parseNumber(arr, n) : 1; + crit = new Dice(); + for (let i = 0; i < count; i++) { + const max = finalResult.maxFace(); + crit.setFace(max, finalResult.get(max)); + finalResult = finalResult.deleteFace(max); + } + critNorm = crit.total(); + crit = op.call(crit, parseBinaryArgument(arg, arr, n)); + critNorm = crit && critNorm ? crit.total() / critNorm : 1; + } + let save; + let saveNorm = 1; + if (arr[0] === "s") { + assertToken(arr, "s"); + assertToken(arr, "a"); + assertToken(arr, "v"); + assertToken(arr, "e"); + save = new Dice(); + const min = finalResult.minFace(); + save.increment(min > 0 ? min : 1, finalResult.get(min)); + saveNorm = save.total(); + finalResult = finalResult.deleteFace(min); + save = op.call(save, parseBinaryArgument(arg, arr, n)); + saveNorm = save && saveNorm ? save.total() / saveNorm : 1; + } + let pc; + let pcNorm = 1; + if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { + assertToken(arr, "p"); + assertToken(arr, "c"); + pc = new Dice(); + const min = finalResult.minFace(); + pc.increment(min > 0 ? min : 1, finalResult.get(min)); + const missBefore = pc.total(); + finalResult = finalResult.deleteFace(min); + pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); + const missAfter = pc ? pc.total() : 0; + pcNorm = missBefore ? missAfter / missBefore : 1; + } + let miss; + let missNorm = 1; + if (arr[0] === "m") { + assertToken(arr, "m"); + assertToken(arr, "i"); + assertToken(arr, "s"); + assertToken(arr, "s"); + miss = new Dice(); + const min = finalResult.minFace(); + miss.increment(min > 0 ? min : 1, finalResult.get(min)); + missNorm = miss.total(); + finalResult = finalResult.deleteFace(min); + miss = op.call(miss, parseBinaryArgument(arg, arr, n)); + missNorm = miss && missNorm ? miss.total() / missNorm : 1; + } + let norm = finalResult.total(); + finalResult = op.call(finalResult, arg); + norm = norm ? finalResult.total() / norm : 1; + if (crit) { + const result2 = combineDiceWithNormalization( + crit, + critNorm, + "crit", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (save) { + const result2 = combineDiceWithNormalization( + save, + saveNorm, + "saveHalf", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (miss) { + const result2 = combineDiceWithNormalization( + miss, + missNorm, + "missDamage", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (pc) { + const result2 = combineDiceWithNormalization( + pc, + pcNorm, + "pc", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + op = parseOperation(arr); + } + return finalResult; +} +function parseArgument(s, n) { + let result = parseArgumentInternal(s, n); + while (true) { + const next = parseArgumentInternal(s, n); + if (next === void 0) break; + result = multiplyDiceByDice(result, next); + } + return result; +} +function multiplyDiceByDice(d1, d2) { + if (typeof d1 === "number") d1 = Dice.scalar(d1); + if (typeof d2 === "number") d2 = Dice.scalar(d2); + const result = new Dice(); + const faces = /* @__PURE__ */ new Map(); + let normalizationFactor = 1; + for (const key of d1.keys()) { + let face; + if (typeof key !== "number") { + continue; + } + if (d2.privateData.keep) { + const faceCount = d2.keys().length; + if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { + throw new DiceParseError( + `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` + ); + } + const repeat = Array(key).fill(d2); + face = opDice(repeat, d2.privateData.keep); + } else { + face = multiplyDice(key, d2); + } + normalizationFactor *= face.total(); + faces.set(key, face); + } + for (const [k, face] of faces) { + const count = d1.get(k); + result.combineInPlace( + face.normalize(count * normalizationFactor / face.total()) + ); + } + result.privateData.except = {}; + return result; +} +function multiplyDice(n, d2) { + if (n > MAX_DICE_COUNT) { + throw new DiceParseError( + `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` + ); + } + if (n === 0) return new Dice(0); + if (n === 1) return d2; + const half = Math.floor(n / 2); + let result = multiplyDice(half, d2); + result = result.add(result); + if (n % 2 === 1) { + result = result.add(d2); + } + return result; +} +function opDice(diceList, keepFn) { + return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); +} +function opDiceInternal(diceList, result, index, values, weight, combineFn) { + if (index === diceList.length) { + return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); + } + const currentDice = diceList[index]; + for (const face of currentDice.keys()) { + values.push(face); + result = opDiceInternal( + diceList, + result, + index + 1, + values, + weight * currentDice.get(face), + combineFn + ); + values.pop(); + } + return result; +} +function parseArgumentInternal(s, n) { + if (s.length === 0) return; + const c = s[0]; + switch (c) { + case "(": + s.shift(); + return assertToken(s, ")", parseExpression(s, n)); + case "h": + case "d": + return parseDice(s, n); + case "k": + assertToken(s, "k"); + return parseKeep(s, n); + case "n": + return parseNumber(s, n); + default: + if (isDigit(c)) return parseNumber(s, n); + return; + } +} +function parseBinaryArgument(arg, arr, n) { + if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { + assertToken(arr, "half"); + const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; + return diceArg.divideRoundDown(2); + } + const parsed = parseArgument(arr, n); + return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; +} +function assertToken(s, expected, ret) { + for (const ch of expected) { + const found = s.shift(); + if (found !== ch) { + throw new Error(`Expected character '${ch}', found '${found}'`); + } + } + return ret; +} +function parseDice(s, n) { + let rerollOne = false; + if (peek(s, "hd") && peekIsNumber(s, 2)) { + assertToken(s, "h"); + assertToken(s, "d"); + rerollOne = true; + } else if (peek(s, "d") && peekIsNumber(s, 1)) { + assertToken(s, "d"); + } else { + return; + } + const sides = parseNumber(s, n); + if (sides > MAX_DIE_SIDES) { + throw new DiceParseError( + `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` + ); + } + let result = new Dice(sides); + if (rerollOne) { + result = result.reroll(1); + } + return result; +} +function peek(arr, expected) { + if (expected.length > arr.length) return false; + for (let i = 0; i < expected.length; i++) { + if (arr[i] !== expected.charAt(i)) return false; + } + return true; +} +function peekIsNumber(arr, index) { + if (index >= arr.length) return false; + return isDigit(arr[index]) || arr[index] === "n"; +} +function parseNumber(s, n) { + let ret = ""; + while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { + const ch = s.shift(); + ret += ch === "n" ? n.toString() : ch; + } + if (ret.length === 0) { + throw new Error(`Expected number, found: '${s[0]}'`); + } + return parseInt(ret, 10); +} +function isDigit(c) { + return c >= "0" && c <= "9"; +} +function parseKeep(s, n) { + let keepLowest = false; + if (peek(s, "l")) { + assertToken(s, "l"); + keepLowest = true; + } else if (peek(s, "h")) { + assertToken(s, "h"); + keepLowest = false; + } else { + return; + } + const keepCount = parseNumber(s, n); + const result = parseArgumentInternal(s, n); + if (result instanceof Dice) { + result.privateData.keep = keepN(keepCount, keepLowest); + return result; + } + throw new Error("Expected Dice after keep modifier"); +} +function keepN(n, low) { + return (values) => { + const sorted = [...values].sort((a, b) => low ? a - b : b - a); + return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); + }; +} +function parseOperation(s) { + switch (s[0]) { + case ")": + return; + case "a": + assertToken(s, "ac"); + return Dice.prototype.ac; + case "d": + assertToken(s, "dc"); + return Dice.prototype.dc; + case "!": + assertToken(s, "!"); + const adv = Dice.prototype.advantage; + adv.unary = true; + return adv; + case ">": + assertToken(s, ">"); + return Dice.prototype.max; + case "<": + assertToken(s, "<"); + return Dice.prototype.min; + case "+": + assertToken(s, "+"); + return Dice.prototype.addNonZero; + case "~": + assertToken(s, "~"); + assertToken(s, "+"); + return Dice.prototype.add; + case "-": + assertToken(s, "-"); + return Dice.prototype.subtract; + case "&": + assertToken(s, "&"); + return Dice.prototype.combine; + case "r": + assertToken(s, "reroll"); + return Dice.prototype.reroll; + case "*": + assertToken(s, "*"); + if (peek(s, "*")) { + assertToken(s, "*"); + return Dice.prototype.multiply; + } + return Dice.prototype.conditionalApply; + case "/": + assertToken(s, "/"); + if (s[0] === "/") { + assertToken(s, "/"); + return Dice.prototype.divideRoundDown; + } + return Dice.prototype.divideRoundUp; + case "=": + assertToken(s, "="); + return Dice.prototype.eq; + } + return; +} + +// src/builder/prob.ts +function d20PmfFromCdf(cdfPow, eps = EPS) { + const out = /* @__PURE__ */ new Map(); + let prev = 0; + for (let k = 1; k <= 20; k++) { + const cur = cdfPow(k); + const pk = cur - prev; + if (pk > 0) { + out.set(k, pk); + } + prev = cur; + } + return PMF.fromMap(out, eps); +} + +// src/builder/d20.ts +var cacheKeyMap = { + "flat-flat": "d20", + "flat-reroll": "hd20", + "advantage-flat": "d20 > d20", + "advantage-reroll": "hd20 > hd20", + "disadvantage-flat": "d20 < d20", + "disadvantage-reroll": "hd20 < hd20", + "elven accuracy-flat": "d20 > d20 > d20", + "elven accuracy-reroll": "hd20 > hd20 > hd20" +}; +function d20RollPMF(rollType, rerollOne = false) { + rollType = rollType || "flat"; + const cacheKeyLookup = `${rollType}-${rerollOne ? "reroll" : "flat"}`; + const cacheKey = cacheKeyMap[cacheKeyLookup]; + if (!cacheKey) { + throw new Error(`Invalid roll type: ${rollType}`); + } + const cached = pmfCache.get(cacheKey); + if (cached) return cached; + const base = d20PMF(rerollOne); + if (!rollType || rollType === "flat") { + pmfCache.set(cacheKey, base); + return base; + } + const p = new Array(21).fill(0); + for (const [r, rec] of base) { + const pr = typeof rec === "number" ? rec : rec.p; + if (r >= 1 && r <= 20) p[r] = pr; + } + const F = new Array(21).fill(0); + for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k]; + const eps = 0; + let result = base; + if (rollType === "advantage") { + result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps); + } else if (rollType === "elven accuracy") { + result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps); + } else if (rollType === "disadvantage") { + result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps); + } + pmfCache.set(cacheKey, result); + return result; +} +function d20PMF(rerollOne) { + const cacheKey = `flat-${rerollOne ? "reroll" : "flat"}`; + const cached = pmfCache.get(cacheKey); + if (cached) return cached; + const m = /* @__PURE__ */ new Map(); + const base = 1 / 20; + const rerollShare = base * base; + if (!rerollOne) { + for (let r = 1; r <= 20; r++) { + m.set(r, base); + } + } else { + for (let r = 1; r <= 20; r++) { + m.set(r, (r === 1 ? 0 : base) + rerollShare); + } + } + const result = PMF.fromMap(m, EPS); + pmfCache.set(cacheKey, result); + return result; +} + +// src/builder/roll.ts +var defaultConfig = { + count: 1, + sides: 0, + modifier: 0, + reroll: 0, + explode: 0, + minimum: 0, + bestOf: 0, + keep: void 0, + rollType: "flat" +}; +var rollConfigsEqual = (a, b) => { + return a.count === b.count && a.sides === b.sides && a.modifier === b.modifier && a.reroll === b.reroll && a.explode === b.explode && a.minimum === b.minimum && a.bestOf === b.bestOf && a.keep === b.keep && a.rollType === b.rollType; +}; +var configComplexityScore = (config) => { + return (config.reroll > 0 ? 1 : 0) + (config.explode > 0 ? 1 : 0) + (config.minimum > 0 ? 1 : 0) + (config.bestOf > 0 ? 1 : 0) + (config.keep !== void 0 ? 1 : 0) + (config.rollType !== "flat" ? 1 : 0); +}; +var RollBuilder = class _RollBuilder { + constructor(countOrConfigs = 1) { + // --- Dice Shortcut Methods --- + this.d4 = () => this.d(4); + this.d6 = () => this.d(6); + this.d8 = () => this.d(8); + this.d10 = () => this.d(10); + this.d12 = () => this.d(12); + this.d20 = () => this.d(20); + this.d100 = () => this.d(100); + if (typeof countOrConfigs === "number") { + const count = countOrConfigs; + if (isNaN(count)) throw new Error("Invalid NaN value for count"); + this.subRollConfigs = [ + { ...defaultConfig, count, isSubtraction: count < 0 } + ]; + } else { + this.subRollConfigs = countOrConfigs.map((c) => ({ ...c })); + } + } + create(configs) { + return new _RollBuilder(configs); + } + get lastConfig() { + return this.subRollConfigs[this.subRollConfigs.length - 1]; + } + hasHiddenState() { + return false; + } + getSubRollConfigs() { + return this.subRollConfigs.map((c) => ({ ...c })); + } + // for testing + static fromConfig(config) { + return new _RollBuilder([{ ...defaultConfig, ...config }]); + } + static fromConfigs(configs) { + return new _RollBuilder( + configs.map((config) => ({ ...defaultConfig, ...config })) + ); + } + static fromArgs(...args) { + if (args.length === 1) { + const arg = args[0]; + if (typeof arg === "number") { + if (isNaN(arg)) throw new Error("Invalid NaN value for argument"); + return new _RollBuilder(0).plus(arg); + } + if (typeof arg === "string") { + return new ParsedRollBuilder(arg); + } + if (arg instanceof _RollBuilder) { + return arg; + } + } + if (args.length === 2 || args.length === 3) { + const [count, sidesOrDie, modifier] = args; + if (typeof count !== "number") { + throw new Error("First argument must be a number for multi-arg call"); + } + if (isNaN(count)) throw new Error("Invalid NaN value for count argument"); + if (sidesOrDie instanceof _RollBuilder) { + if (sidesOrDie.hasHiddenState()) { + throw new Error( + "Cannot use a roll with hidden state (like a pooled roll) as a die type." + ); + } + const subRollConfigs = sidesOrDie.getSubRollConfigs(); + if (subRollConfigs.length === 0) { + const result = new _RollBuilder(0); + return modifier !== void 0 ? result.plus(modifier) : result; + } + const absCount = Math.abs(count); + const newConfigs = subRollConfigs.map((config) => ({ + ...config, + count: config.count * absCount, + modifier: config.modifier * absCount + })); + let resultBuilder = new _RollBuilder(newConfigs); + if (count < 0) { + const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); + resultBuilder = new _RollBuilder(negatedConfigs); + } + return modifier !== void 0 ? resultBuilder.plus(modifier) : resultBuilder; + } else if (typeof sidesOrDie === "number" || sidesOrDie === void 0) { + if (typeof sidesOrDie === "number" && isNaN(sidesOrDie)) + throw new Error("Invalid NaN value for sides argument"); + let builder = new _RollBuilder(count); + if (sidesOrDie && sidesOrDie > 0) { + builder = builder.d(sidesOrDie); + } + return modifier !== void 0 ? builder.plus(modifier) : builder; + } + } + throw new Error(`Invalid arguments passed: ${args.join(", ")}`); + } + // --- Core Dice Methods --- + d(sides) { + if (sides !== void 0 && isNaN(sides)) + throw new Error("Invalid NaN value for sides"); + if (sides === void 0) return this; + if (this.lastConfig.sides && this.lastConfig.sides > 0) { + throw new Error("Cannot add a die after adding a die"); + } + if (sides === 0) return this; + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].sides = sides; + return this.create(newConfigs); + } + plus(modOrRoll, die) { + if (typeof modOrRoll === "number" && isNaN(modOrRoll)) + throw new Error("Invalid NaN value for modOrRoll"); + if (die instanceof _RollBuilder && typeof modOrRoll === "number") { + if (die.hasHiddenState()) { + throw new Error( + "Cannot use a roll with hidden state (like a pooled roll) as a die type." + ); + } + const count = modOrRoll; + const subRollConfigs = die.getSubRollConfigs(); + if (subRollConfigs.length === 0) return this; + const absCount = Math.abs(count); + const newConfigs = subRollConfigs.map((config) => ({ + ...config, + count: config.count * absCount, + modifier: config.modifier * absCount + })); + let rollToAdd = new _RollBuilder(newConfigs); + if (count < 0) { + const negatedConfigs = rollToAdd.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); + rollToAdd = new _RollBuilder(negatedConfigs); + } + return this.add(rollToAdd); + } + if (die !== void 0) { + throw new Error("Invalid arguments to plus()"); + } + if (modOrRoll === void 0) return this; + if (typeof modOrRoll === "number") { + if (modOrRoll === 0) return this; + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].modifier += modOrRoll; + return this.create(newConfigs); + } + return this.add(modOrRoll); + } + minus(modOrRoll, die) { + const isNumber = typeof modOrRoll === "number"; + const dieIsRoll = die instanceof _RollBuilder; + if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die); + if (die !== void 0) throw new Error("Invalid arguments to minus()"); + if (modOrRoll === void 0) return this; + return isNumber ? this.plus(-modOrRoll) : this.plus(-1, modOrRoll); + } + /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ + reroll(value) { + if (isNaN(value)) throw new Error("Invalid NaN value for reroll"); + if (value === this.lastConfig.reroll) return this; + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].reroll = value; + return this.create(newConfigs); + } + /** Set finite explode count for max-face explosions (Infinity allowed). */ + explode(count = Infinity) { + if (count !== void 0 && isNaN(count)) + throw new Error("Invalid NaN value for explode count"); + if (count === void 0) return this; + if (count === 0) return this; + if (count < 0) throw new Error("Explode count must be >= 0"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].explode = count; + return this.create(newConfigs); + } + /** Apply per-die minimum value (min > 0). */ + minimum(val) { + if (val !== void 0 && isNaN(val)) + throw new Error("Invalid NaN value for minimum"); + if (val === void 0) return this; + if (val === 0) return this; + if (val < 0) throw new Error("Minimum value must be >= 0"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].minimum = val + 1; + return this.create(newConfigs); + } + bestOf(count) { + if (count !== void 0 && isNaN(count)) + throw new Error("Invalid NaN value for bestOf count"); + if (count === void 0) return this; + if (count <= 0) throw new Error("Best of count must be > 0"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].bestOf = count; + return this.create(newConfigs); + } + keepHighest(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepHighest"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].keep = { total, count, mode: "highest" }; + return this.create(newConfigs); + } + keepLowest(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepLowest"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].keep = { total, count, mode: "lowest" }; + return this.create(newConfigs); + } + keepHighestAll(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepHighestAll"); + const currentAST = this.toAST(); + const trialPool = { + type: "sum", + count: total, + child: currentAST + }; + const keepNode = { + type: "keep", + mode: "highest", + count, + child: trialPool + }; + const currentExpr = this.toExpression(); + const expression = `${total}kh${count}(${currentExpr})`; + return new PooledRollBuilder(keepNode, expression); + } + keepLowestAll(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepLowestAll"); + const currentAST = this.toAST(); + const trialPool = { + type: "sum", + count: total, + child: currentAST + }; + const keepNode = { + type: "keep", + mode: "lowest", + count, + child: trialPool + }; + const currentExpr = this.toExpression(); + const expression = `${total}kl${count}(${currentExpr})`; + return new PooledRollBuilder(keepNode, expression); + } + withAdvantage() { + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].rollType = "advantage"; + return this.create(newConfigs); + } + withDisadvantage() { + const configs = this.getSubRollConfigs(); + configs[configs.length - 1].rollType = "disadvantage"; + return this.create(configs); + } + add(anotherRoll) { + if (anotherRoll === void 0) return this; + if (anotherRoll.hasHiddenState()) { + throw new Error( + "Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll)." + ); + } + const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; + return this.create(configs); + } + withBonus(anotherRoll) { + const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; + return this.create(configs); + } + addRoll(count = 1) { + if (isNaN(count)) throw new Error("Invalid NaN value for count"); + const configs = [ + ...this.subRollConfigs, + { + ...defaultConfig, + count, + isSubtraction: count < 0 + } + ]; + return this.create(configs); + } + scaleDice(scale) { + const scaleInt = Math.floor(scale); + if (scaleInt !== scale) throw new Error("Scale must be an integer"); + if (scaleInt <= 0) throw new Error("Scale must be > 0"); + const newConfigs = this.getSubRollConfigs().map((config) => { + if (!config.sides || config.sides <= 0) return config; + return { ...config, count: config.count * scaleInt }; + }); + return this.create(newConfigs); + } + doubleDice() { + return this.scaleDice(2); + } + alwaysHits() { + return new AlwaysHitBuilder(this); + } + alwaysCrits() { + return new AlwaysCritBuilder(this); + } + copy() { + return this.create(this.getSubRollConfigs()); + } + withElvenAccuracy() { + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].rollType = "elven accuracy"; + return this.create(newConfigs); + } + toExpression() { + const originalDiceConfigs = this.subRollConfigs.filter( + (config) => config.sides && config.sides > 0 + ); + const configGroups = /* @__PURE__ */ new Map(); + for (const config of originalDiceConfigs) { + const keyConfig = { ...config }; + delete keyConfig.count; + delete keyConfig.modifier; + const key = JSON.stringify(keyConfig); + const existingGroup = configGroups.get(key); + if (existingGroup) { + existingGroup.totalCount += config.count; + } else { + configGroups.set(key, { config, totalCount: config.count }); + } + } + const rootConfig = this.getRootDieConfig(); + const groupedConfigs = Array.from(configGroups.values()); + let rootD20Group; + if (rootConfig && rootConfig.sides === 20) { + const rootIndex = groupedConfigs.findIndex( + ({ config }) => rollConfigsEqual(config, rootConfig) && JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep) + ); + if (rootIndex !== -1) { + rootD20Group = groupedConfigs.splice(rootIndex, 1)[0]; + } + } + const sortedDiceConfigs = groupedConfigs.map(({ config, totalCount }) => ({ + ...config, + count: totalCount + })).sort((a, b) => { + const aHasPriority = a.reroll > 0 || a.minimum > 0; + const bHasPriority = b.reroll > 0 || b.minimum > 0; + if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1; + if (b.sides !== a.sides) return b.sides - a.sides; + return configComplexityScore(b) - configComplexityScore(a); + }); + const diceConfigs = rootD20Group ? [ + { ...rootD20Group.config, count: rootD20Group.totalCount }, + ...sortedDiceConfigs + ] : sortedDiceConfigs; + const totalModifier = this.subRollConfigs.reduce( + (sum, config) => sum + config.modifier, + 0 + ); + if (diceConfigs.length === 0) return totalModifier.toString(); + const rootDieConfig = this.getRootDieConfig(); + const newRootConfig = rootDieConfig ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig)) : void 0; + const diceExpressions = diceConfigs.map( + (config) => this.configToSingleExpressionWithoutModifier( + config, + config === newRootConfig + ) + ); + let result = ""; + for (let i = 0; i < diceExpressions.length; i++) { + const config = diceConfigs[i]; + const expression = diceExpressions[i]; + if (i === 0) { + result = (config.isSubtraction ? "-" : "") + expression; + if (config.sides === 20 && totalModifier !== 0) { + if (totalModifier > 0) result += ` + ${totalModifier}`; + else result += ` - ${Math.abs(totalModifier)}`; + } + } else { + const operator = config.isSubtraction ? " - " : " + "; + result += operator + expression; + } + } + if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) { + if (totalModifier > 0) result += ` + ${totalModifier}`; + else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`; + } + return result.replace(/\+ -/g, "-"); + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + get pmf() { + return this.toPMF(); + } + toQuery(eps = 0) { + return this.toPMF(eps).query(); + } + toAST() { + const configs = this.getSubRollConfigs(); + return astFromRollConfigs(configs) || { type: "constant", value: 0 }; + } + configToSingleExpressionWithoutModifier(config, isRootDie) { + if (!config.sides || config.sides <= 0) return ""; + let baseDie = `d${config.sides}`; + if (config.reroll > 0) { + if (config.minimum > 0 && config.explode > 0) ; else if (config.minimum > 0) { + for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`; + } else { + for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`; + } + } + if (config.minimum > 0) { + if (config.reroll > 0 && !config.explode) { + baseDie = `${config.minimum}>(${baseDie})`; + } else { + baseDie = `${config.minimum}>${baseDie}`; + } + if (config.reroll > 0 && config.explode > 0) { + for (let i = 1; i <= config.reroll; i++) { + baseDie += ` reroll ${i}`; + } + } + } + if (baseDie === "d20 reroll 1" && config.minimum <= 1) baseDie = "hd20"; + let mainExpression = ""; + switch (config.rollType) { + case "advantage": + mainExpression = `${baseDie} > ${baseDie}`; + break; + case "disadvantage": + mainExpression = `${baseDie} < ${baseDie}`; + break; + case "elven accuracy": + mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`; + break; + case "flat": + if (config.keep) { + const mode = config.keep.mode === "highest" ? "kh" : "kl"; + const baseDieExpression = this.configToSingleExpressionWithoutModifier( + { + ...config, + count: config.count, + modifier: 0, + rollType: "flat", + keep: void 0 + }, + false + ); + mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`; + } else { + const isComplex = baseDie.length > `d${config.sides}`.length; + const isHalflingShorthand = baseDie === "hd20"; + const isD20Shorthand = baseDie === "d20" && isRootDie; + const hasMinimum = config.minimum > 0; + const hasReroll = config.reroll > 0; + const effectiveCount = config.isSubtraction ? Math.abs(config.count) : config.count < 0 ? 1 : Math.abs(config.count); + if (effectiveCount > 1) { + const shouldAddParentheses = isComplex; + mainExpression = shouldAddParentheses ? `${effectiveCount}(${baseDie})` : `${effectiveCount}${baseDie}`; + } else if (effectiveCount === 1) { + const needsParens = hasReroll && hasMinimum; + if (config.isSubtraction) { + mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; + } else if (isComplex || isHalflingShorthand || isD20Shorthand || config.count < 0) { + mainExpression = needsParens ? `1(${baseDie})` : baseDie; + } else { + mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; + } + } else { + mainExpression = baseDie; + } + } + if (config.bestOf && config.count && config.bestOf < config.count) { + mainExpression += `kh${config.bestOf}`; + } + break; + } + return mainExpression; + } + getRootDieConfig() { + const configs = this.subRollConfigs; + return configs.find((config) => config.sides > 0) || configs[0]; + } + getAllDieConfigs() { + return this.getSubRollConfigs(); + } + getBonusDiceConfigs() { + const allConfigs = this.subRollConfigs; + const rootConfig = allConfigs.find((config) => config.sides > 0) || allConfigs[0]; + if (!rootConfig) return []; + return allConfigs.filter((config) => config.sides > 0).filter((config) => config !== rootConfig); + } + getBonusDicePMFs(check, eps = 0) { + return check.getBonusDiceConfigs().map( + (config) => pmfFromRollBuilder(_RollBuilder.fromConfigs([config]), eps) + ); + } + get modifier() { + return this.subRollConfigs.reduce( + (sum, config) => sum + config.modifier, + 0 + ); + } + get rollType() { + const rootConfig = this.getRootDieConfig(); + return rootConfig?.rollType || "flat"; + } + get baseReroll() { + const rootConfig = this.getRootDieConfig(); + return rootConfig?.reroll || 0; + } + half() { + return new HalfRollBuilder(this); + } + /** + * Scale this roll's result by `numerator / denominator`, rounding each outcome. + * A general, composable form of {@link half} — used to model damage-type resistance + * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). + * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. + */ + scaleResult(numerator, denominator = 1, rounding = "floor") { + return new ScaleRollBuilder(this, numerator, denominator, rounding); + } + // Create a "max of N rolls" version of this roll for crit damage with keep operations + maxOf(count) { + return new MaxOfRollBuilder(this, count); + } + // These methods are implemented via prototype augmentation in ac.ts and dc.ts + // They are declared here to provide proper TypeScript types + ac(_targetAC) { + throw new Error("ac() should be implemented via prototype augmentation"); + } + dc(_saveDC) { + throw new Error("dc() should be implemented via prototype augmentation"); + } +}; +var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder { + constructor(innerRoll) { + super(0); + this.innerRoll = innerRoll; + } + hasHiddenState() { + return this.innerRoll.hasHiddenState(); + } + // No need to override create if we don't expose RollBuilder methods that use it, + // but HalfRollBuilder extends RollBuilder so it does. + // However, HalfRollBuilder seems to just wrap another roll. + // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder? + // No, RollBuilder.plus returns RollBuilder. + // The inheritance here is a bit tricky. + // Existing code for HalfRollBuilder doesn't seem to implement plus/etc. + // So .plus() on a HalfRollBuilder would return a RollBuilder (base class). + // Which is fine. + // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that. + get lastConfig() { + return this.innerRoll.lastConfig; + } + getSubRollConfigs() { + return this.innerRoll.getSubRollConfigs(); + } + toExpression() { + const innerExpression = this.innerRoll.toExpression(); + return `(${innerExpression}) // 2`; + } + toAST() { + return { + type: "half", + child: this.innerRoll.toAST() + }; + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _HalfRollBuilder(this.innerRoll.copy()); + } +}; +var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder { + constructor(innerRoll, numerator, denominator = 1, rounding = "floor") { + super(0); + this.innerRoll = innerRoll; + this.numerator = numerator; + this.denominator = denominator; + this.rounding = rounding; + } + hasHiddenState() { + return this.innerRoll.hasHiddenState(); + } + get lastConfig() { + return this.innerRoll.lastConfig; + } + getSubRollConfigs() { + return this.innerRoll.getSubRollConfigs(); + } + toExpression() { + const inner = this.innerRoll.toExpression(); + if (this.denominator === 1) return `${this.numerator} * (${inner})`; + if (this.numerator === 1) return `(${inner}) // ${this.denominator}`; + return `(${inner}) * ${this.numerator} // ${this.denominator}`; + } + toAST() { + return { + type: "scale", + numerator: this.numerator, + denominator: this.denominator, + rounding: this.rounding, + child: this.innerRoll.toAST() + }; + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _ScaleRollBuilder( + this.innerRoll.copy(), + this.numerator, + this.denominator, + this.rounding + ); + } +}; +var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder { + constructor(innerRoll, count, diceCount, diceSides) { + super(0); + this.innerRoll = innerRoll; + this.count = count; + this.diceCount = diceCount; + this.diceSides = diceSides; + } + hasHiddenState() { + return this.innerRoll.hasHiddenState(); + } + get lastConfig() { + return this.innerRoll.lastConfig; + } + getSubRollConfigs() { + return this.innerRoll.getSubRollConfigs(); + } + toExpression() { + if (this.diceCount && this.diceSides) { + return `max${this.count}(${this.diceCount}d${this.diceSides})`; + } + return `max${this.count}(?d?)`; + } + toAST() { + if (this.diceCount && this.diceSides) { + const sumChild = { + type: "sum", + count: this.diceCount, + child: { type: "die", sides: this.diceSides } + }; + return { + type: "maxOf", + count: this.count, + child: sumChild + }; + } + try { + const configs = this.innerRoll.getSubRollConfigs(); + if (configs.length === 1 && configs[0].sides) { + const config = configs[0]; + const sumChild = { + type: "sum", + count: config.count, + child: { type: "die", sides: config.sides } + }; + return { + type: "maxOf", + count: this.count, + child: sumChild + }; + } + } catch { + } + throw new Error( + `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration` + ); + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count); + } +}; +var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder { + constructor(baseRoll, attackConfig) { + if (baseRoll.hasHiddenState()) { + throw new Error( + "Cannot create AlwaysHitBuilder from a roll with hidden state." + ); + } + super(baseRoll.getSubRollConfigs()); + if (attackConfig) { + this.attackConfig = { ...attackConfig }; + } else { + this.attackConfig = { critThreshold: 20 }; + } + } + create(configs) { + return new RollBuilder(configs); + } + onHit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new AttackBuilder(this, damageRoll); + } + get critThreshold() { + return this.attackConfig.critThreshold; + } + // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? + critOn(critThreshold) { + const newConfig = { critThreshold }; + return new _AlwaysHitBuilder(this, newConfig); + } + alwaysCrits() { + return new AlwaysCritBuilder(this, void 0, true); + } + // Legacy expressions + toExpression() { + const configs = this.getSubRollConfigs(); + return new RollBuilder(configs).toExpression(); + } + toPMF() { + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + return d20RollPMF(rollType, rerollOne); + } + copy() { + const baseCopy = new RollBuilder(this.getSubRollConfigs()); + const critThreshold = this.critThreshold; + const newConfig = { critThreshold }; + return new _AlwaysHitBuilder(baseCopy, newConfig); + } +}; +var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder { + constructor(baseRoll, attackConfig, fromAlwaysHit = false) { + if (baseRoll.hasHiddenState()) { + throw new Error( + "Cannot create AlwaysCritBuilder from a roll with hidden state." + ); + } + super(baseRoll.getSubRollConfigs()); + if (attackConfig) { + this.attackConfig = { ...attackConfig }; + } else { + this.attackConfig = { critThreshold: 20 }; + } + this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder; + } + create(configs) { + return new RollBuilder(configs); + } + onHit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new AttackBuilder(this, damageRoll); + } + get critThreshold() { + return this.attackConfig.critThreshold; + } + critOn(critThreshold) { + const newConfig = { critThreshold, ac: this.attackConfig.ac }; + return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit); + } + // Legacy expressions + toExpression() { + const configs = this.getSubRollConfigs(); + return new RollBuilder(configs).toExpression(); + } + toPMF() { + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + return d20RollPMF(rollType, rerollOne); + } + copy() { + const baseCopy = new RollBuilder(this.getSubRollConfigs()); + const critThreshold = this.critThreshold; + const newConfig = { critThreshold, ac: this.attackConfig.ac }; + return new _AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit); + } +}; +var ParsedRollBuilder = class _ParsedRollBuilder extends RollBuilder { + constructor(expression) { + super([]); + this.originalExpression = expression; + this.cachedPMF = parse(expression, 0); + } + hasHiddenState() { + return true; + } + create(configs) { + return new RollBuilder(configs); + } + toPMF(_eps = 0) { + return this.cachedPMF; + } + toExpression() { + return this.originalExpression; + } + toAST() { + throw new Error( + "ParsedRollBuilder does not support AST conversion. Use the builder API instead." + ); + } + copy() { + return new _ParsedRollBuilder(this.originalExpression); + } + doubleDice() { + throw new Error( + "ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead." + ); + } +}; +var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder { + constructor(baseAST, baseExpression, configs = []) { + super(configs.length > 0 ? configs : 0); + this.baseAST = baseAST; + this.baseExpression = baseExpression; + } + create(configs) { + return new _PooledRollBuilder(this.baseAST, this.baseExpression, configs); + } + hasHiddenState() { + return true; + } + d(_sides) { + throw new Error("Cannot add dice to a pooled roll. The pool is finalized."); + } + reroll(_value) { + throw new Error("Cannot set reroll on a pooled roll."); + } + explode(_count = Infinity) { + throw new Error("Cannot set explode on a pooled roll."); + } + minimum(_val) { + throw new Error("Cannot set minimum on a pooled roll."); + } + bestOf(_count) { + throw new Error("Cannot set bestOf on a pooled roll."); + } + keepHighest(_total, _count) { + throw new Error( + "Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling." + ); + } + keepLowest(_total, _count) { + throw new Error( + "Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling." + ); + } + withAdvantage() { + throw new Error("Cannot set advantage on a pooled roll."); + } + withDisadvantage() { + throw new Error("Cannot set disadvantage on a pooled roll."); + } + withElvenAccuracy() { + throw new Error("Cannot set elven accuracy on a pooled roll."); + } + toAST() { + const configsAST = super.toAST(); + const isZero = configsAST.type === "constant" && configsAST.value === 0; + if (isZero) { + return this.baseAST; + } + const children = [ + { node: this.baseAST, sign: 1 }, + { node: configsAST, sign: 1 } + ]; + return { type: "add", children }; + } + toExpression() { + const configsExpression = super.toExpression(); + if (configsExpression === "0") { + return this.baseExpression; + } + if (configsExpression.startsWith("-")) { + return `${this.baseExpression} - ${configsExpression.substring(1)}`; + } + return `${this.baseExpression} + ${configsExpression}`; + } + copy() { + return new _PooledRollBuilder( + this.baseAST, + this.baseExpression, + this.getSubRollConfigs() + ); + } + scaleDice(scale) { + const scaleInt = Math.floor(scale); + if (scaleInt !== scale) throw new Error("Scale must be an integer"); + if (scaleInt <= 0) throw new Error("Scale must be > 0"); + const newBaseAST = { + type: "sum", + count: scaleInt, + child: this.baseAST + }; + const newBaseExpr = scaleInt === 1 ? this.baseExpression : `${scaleInt}(${this.baseExpression})`; + return new _PooledRollBuilder( + newBaseAST, + newBaseExpr, + this.getSubRollConfigs() + ); + } + times(count) { + if (isNaN(count)) throw new Error("Invalid NaN value for times"); + if (Math.floor(count) !== count) + throw new Error("times() requires an integer"); + if (count < 0) throw new Error("times() requires a non-negative integer"); + const currentAST = this.toAST(); + const currentExpr = this.toExpression(); + const sumNode = { + type: "sum", + count, + child: currentAST + }; + const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`; + return new _PooledRollBuilder(sumNode, newExpr); + } +}; +var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder { + constructor(parts) { + super(0); + this.parts = parts; + } + hasHiddenState() { + return true; + } + getSubRollConfigs() { + return []; + } + toAST() { + return { + type: "add", + children: this.parts.map((p) => ({ + node: p.toAST(), + sign: 1 + })) + }; + } + toExpression() { + const exprs = this.parts.map((p) => p.toExpression()).filter((e) => e && e !== "0"); + if (exprs.length === 0) return "0"; + let result = exprs[0]; + for (let i = 1; i < exprs.length; i++) { + const e = exprs[i]; + result += e.startsWith("-") ? ` - ${e.substring(1)}` : ` + ${e}`; + } + return result.replace(/\+ -/g, "-"); + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _CompositeSumRollBuilder(this.parts.map((p) => p.copy())); + } +}; +function sumRolls(parts) { + const meaningful = parts.filter((p) => p !== void 0); + if (meaningful.length === 0) return new RollBuilder(0); + if (meaningful.length === 1) return meaningful[0]; + return new CompositeSumRollBuilder(meaningful); +} + +// src/builder/factory.ts +var rollFn = (count, sidesOrDie, modifier) => { + if (sidesOrDie instanceof RollBuilder) { + if (sidesOrDie.hasHiddenState()) { + throw new Error( + "Cannot use a roll with hidden state (like a pooled roll) as a die type." + ); + } + const subRollConfigs = sidesOrDie.getSubRollConfigs(); + if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier); + const absCount = Math.abs(count); + const newConfigs = subRollConfigs.map((config) => ({ + ...config, + count: config.count * absCount, + modifier: config.modifier * absCount + })); + let resultBuilder = new RollBuilder(newConfigs); + if (count < 0) { + const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); + resultBuilder = new RollBuilder(negatedConfigs); + } + return resultBuilder.plus(modifier); + } else { + let builder = new RollBuilder(count); + if (sidesOrDie && sidesOrDie > 0) { + builder = builder.d(sidesOrDie); + } + return builder.plus(modifier); + } +}; +rollFn.d = (sides) => { + if (typeof sides === "string") { + return RollBuilder.fromArgs(sides); + } + return new RollBuilder(1).d(sides); +}; +rollFn.hd20 = () => new RollBuilder(1).d20().reroll(1); +rollFn.d4 = () => new RollBuilder(1).d4(); +rollFn.d6 = () => new RollBuilder(1).d6(); +rollFn.d8 = () => new RollBuilder(1).d8(); +rollFn.d10 = () => new RollBuilder(1).d10(); +rollFn.d12 = () => new RollBuilder(1).d12(); +rollFn.d20 = () => new RollBuilder(1).d20(); +rollFn.d100 = () => new RollBuilder(1).d100(); +rollFn.flat = (n) => new RollBuilder(0).plus(n); +function d(sides) { + if (typeof sides === "string") { + return RollBuilder.fromArgs(sides); + } + return new RollBuilder(1).d(sides); +} +var d4 = new RollBuilder(1).d4(); +var d6 = new RollBuilder(1).d6(); +var d8 = new RollBuilder(1).d8(); +var d10 = new RollBuilder(1).d10(); +var d12 = new RollBuilder(1).d12(); +var d20 = new RollBuilder(1).d20(); +var hd20 = new RollBuilder(1).d20().reroll(1); +var d100 = new RollBuilder(1).d100(); +var flat = (n) => new RollBuilder(0).plus(n); +var roll = rollFn; +var builderPMFCache = new LRUCache(1e3); + +// src/builder/ast.ts +var defaultEps = 0; +var singleDiePMFCache = new LRUCache(1e3); +function astFromRollConfigs(configs) { + if (!configs || configs.length === 0) return void 0; + const children = []; + let constantSum = 0; + for (const cfg of configs) { + const sign = cfg.isSubtraction || cfg.count < 0 ? -1 : 1; + const count = Math.abs(cfg.count || 0); + constantSum += cfg.modifier || 0; + if ((cfg.sides || 0) <= 0) continue; + const die = { + type: "die", + sides: cfg.sides, + reroll: cfg.reroll > 0 ? cfg.reroll : void 0, + minimum: cfg.minimum > 0 ? cfg.minimum : void 0, + explode: cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0 ? cfg.explode : void 0 + }; + let node = die; + let appliedRollType = false; + if (cfg.rollType && cfg.rollType !== "flat") { + if (cfg.sides === 20) { + node = { + type: "d20Roll", + rollType: cfg.rollType, + child: node + }; + } else { + const n = cfg.rollType === "elven accuracy" ? 3 : 2; + const mode = cfg.rollType === "disadvantage" ? "lowest" : "highest"; + const base = { type: "sum", count: n, child: node }; + node = { type: "keep", mode, count: 1, child: base }; + } + appliedRollType = true; + } + if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) { + const baseCount = Math.max(1, Math.floor(Math.abs(count || 1))); + const trials = Math.max(1, Math.floor(cfg.keep.total)); + const k = Math.max(0, Math.floor(cfg.keep.count)); + if (k === 1 && cfg.keep.mode === "highest") { + const perTrial = { + type: "sum", + count: baseCount, + child: node + }; + if (trials === 1) { + node = perTrial; + } else { + node = { + type: "maxOf", + count: trials, + child: perTrial + }; + } + } else if (trials === baseCount) { + const base = { type: "sum", count: trials, child: node }; + node = { + type: "keep", + mode: cfg.keep.mode, + count: k, + child: base + }; + } else { + const perTrial = { + type: "sum", + count: baseCount, + child: node + }; + if (trials === 1) { + node = perTrial; + } else { + const trialPool = { + type: "sum", + count: trials, + child: perTrial + }; + node = { + type: "keep", + mode: cfg.keep.mode, + count: k, + child: trialPool + }; + } + } + } else { + const c = appliedRollType ? 1 : Math.max(1, count || 1); + node = { type: "sum", count: c, child: node }; + } + children.push({ node, sign }); + } + if (children.length === 0) { + return { type: "constant", value: constantSum }; + } + const add = { type: "add", children }; + if (constantSum !== 0) + add.children.push({ + node: { type: "constant", value: constantSum }, + sign: 1 + }); + return add; +} +function resolve(node, eps = defaultEps) { + const signature = getASTSignature(node); + const cacheKey = `${signature}_${eps}`; + const cached = builderPMFCache.get(cacheKey); + if (cached) return cached; + const result = (() => { + switch (node.type) { + case "constant": + return PMF.delta(node.value, eps); + case "die": { + return resolveSingleDie(node, eps); + } + case "sum": { + const base = resolve(node.child, eps); + const n = Math.max(0, Math.floor(node.count)); + if (n === 0) return PMF.delta(0, eps); + if (n === 1) return base; + return base.power(n, eps); + } + case "add": { + let shift = 0; + const parts = []; + for (const c of node.children) { + if (c.node.type === "constant") { + shift += c.sign * c.node.value; + } else { + const p = resolve(c.node, eps); + parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v)); + } + } + if (parts.length === 0) return PMF.delta(shift, eps); + let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps); + if (shift !== 0) res = res.mapDamage((v) => v + shift); + return res; + } + case "keep": { + const totalTrials = getTotalCount(node); + const keepCount = Math.max(0, Math.min(node.count, totalTrials)); + if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps); + const perTrialNode = node.child.child; + const perTrialPMF = resolve(perTrialNode, eps); + return keepSumPMF( + perTrialPMF, + totalTrials, + keepCount, + node.mode === "highest", + eps + ); + } + case "d20Roll": { + const childDie = findDie(node.child); + const rerollOne = !!childDie && (childDie.reroll || 0) >= 1; + return d20RollPMF(node.rollType, rerollOne); + } + case "half": { + const childPMF = resolve(node.child, eps); + return childPMF.scaleDamage(0.5, "floor"); + } + case "maxOf": { + const childPMF = resolve(node.child, eps); + const count = Math.max(1, Math.floor(node.count)); + if (count === 1) return childPMF; + return computeMaxOfPMF(childPMF, count, eps); + } + case "scale": { + const childPMF = resolve(node.child, eps); + const denom = node.denominator === 0 ? 1 : node.denominator; + return childPMF.scaleDamage(node.numerator / denom, node.rounding); + } + } + })(); + builderPMFCache.set(cacheKey, result); + return result; +} +function pmfFromRollBuilder(rb, eps = defaultEps) { + const ast = rb.toAST(); + return resolve(ast, eps); +} +function resolveSingleDie(die, eps = defaultEps) { + const signature = getASTSignature(die); + const cacheKey = `${signature}_${eps}`; + const cached = singleDiePMFCache.get(cacheKey); + if (cached) return cached; + const s = Math.max(0, Math.floor(die.sides)); + if (s <= 0) return PMF.delta(0, eps); + let probs = /* @__PURE__ */ new Map(); + for (let v = 1; v <= s; v++) probs.set(v, 1 / s); + const r = Math.max(0, Math.floor(die.reroll || 0)); + if (r > 0) { + const k = Math.min(r, s); + const rerollMass = k / s; + const uniformReroll = rerollMass / s; + const next = /* @__PURE__ */ new Map(); + for (let v = 1; v <= s; v++) { + const keep = v <= k ? 0 : 1 / s; + next.set(v, keep + uniformReroll); + } + probs = next; + } + let pmf = PMF.fromMap(new Map(probs), eps); + const minV = Math.max(0, Math.floor(die.minimum || 0)); + if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV)); + const explode = die.explode; + if (explode && Number.isFinite(explode) && explode > 0) { + const times = Math.floor(explode); + const maxFace = s; + const nonMax = /* @__PURE__ */ new Map(); + const pMax = pmf.pAt(maxFace); + for (const v of pmf.support()) { + if (v !== maxFace) nonMax.set(v, pmf.pAt(v)); + } + let nonMaxPMF = PMF.fromMap(nonMax, eps); + if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) { + nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax); + } + let tail = PMF.delta(0, eps); + const addOnce = pmf; + for (let t = 1; t <= times; t++) { + tail = tail.convolve(addOnce, eps); + } + const exploded = PMF.branch( + tail.mapDamage((v) => v + maxFace), + nonMaxPMF, + pMax + ); + pmf = exploded; + } + singleDiePMFCache.set(cacheKey, pmf); + return pmf; +} +function findDie(node) { + switch (node.type) { + case "die": + return node; + case "constant": + return void 0; + case "sum": + case "d20Roll": + case "half": + case "maxOf": + case "scale": + return findDie(node.child); + case "keep": + return findDie(node.child.child); + case "add": + for (const c of node.children) { + const d2 = findDie(c.node); + if (d2) return d2; + } + return void 0; + } +} +function getTotalCount(node) { + let cur = node.child; + while (cur.type === "keep") cur = cur.child; + return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0; +} +function computeMaxOfPMF(pmf, count, eps = defaultEps) { + if (count <= 1) return pmf; + const support = pmf.support(); + const out = /* @__PURE__ */ new Map(); + if (count <= 6 && support.length <= 20) { + let dfs2 = function(rollsLeft, currentMax, probability) { + if (rollsLeft === 0) { + out.set(currentMax, (out.get(currentMax) || 0) + probability); + return; + } + for (const value of support) { + const p = pmf.pAt(value); + if (p > 0) { + const newMax = Math.max(currentMax, value); + dfs2(rollsLeft - 1, newMax, probability * p); + } + } + }; + dfs2(count, -Infinity, 1); + } else { + const sortedSupport = [...support].sort((a, b) => a - b); + let runningCdf = 0; + for (const value of sortedSupport) { + const prevCdf = runningCdf; + runningCdf += pmf.pAt(value); + const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count); + if (probMax > eps) { + out.set(value, probMax); + } + } + } + return PMF.fromMap(out, eps); +} +function keepSumPMF(single, total, keep, highest, eps = defaultEps) { + if (keep >= total) return single.power(total, eps); + if (keep <= 0) return PMF.delta(0, eps); + const sortedSupport = [...single.support()].sort((a, b) => a - b); + const pmfSig = sortedSupport.map((val) => `${val}:${single.pAt(val).toPrecision(6)}`).join(","); + const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`; + const cached = builderPMFCache.get(cacheKey); + if (cached) return cached; + if (keep === 1) { + if (highest) { + return computeMaxOfPMF(single, total, eps); + } else { + const neg = single.mapDamage((v) => -v); + const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v); + builderPMFCache.set(cacheKey, minPMF); + return minPMF; + } + } + let state = /* @__PURE__ */ new Map(); + const stride = total + 1; + const keyOf = (used, r) => used * stride + r; + state.set(keyOf(0, total), /* @__PURE__ */ new Map([[0, 1]])); + const valuesDesc = highest ? [...sortedSupport].sort((a, b) => b - a) : [...sortedSupport].sort((a, b) => a - b); + const binomPMF = (r, p) => { + if (r <= 0) return [1]; + if (p <= eps) { + const arr2 = new Array(r + 1).fill(0); + arr2[0] = 1; + return arr2; + } + if (1 - p <= eps) { + const arr2 = new Array(r + 1).fill(0); + arr2[r] = 1; + return arr2; + } + const q = 1 - p; + const arr = new Array(r + 1).fill(0); + arr[0] = Math.pow(q, r); + const ratio = p / q; + for (let x = 1; x <= r; x++) + arr[x] = arr[x - 1] * (r - x + 1) / x * ratio; + let s = 0; + for (let x = 0; x <= r; x++) s += arr[x]; + if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s; + return arr; + }; + const pruneMap = (m, threshold) => { + if (threshold <= 0) return m; + const out = /* @__PURE__ */ new Map(); + for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr); + return out.size === m.size ? m : out; + }; + const pruneState = (st, threshold) => { + if (threshold <= 0) return st; + const out = /* @__PURE__ */ new Map(); + for (const [k, m] of st) { + const mm = pruneMap(m, threshold); + if (mm.size > 0) out.set(k, mm); + } + return out; + }; + let processedMass = 0; + for (const v of valuesDesc) { + const p = single.pAt(v); + if (p <= 0) continue; + const q = Math.max(eps, 1 - processedMass); + const pCond = Math.min(1, p / q); + const next = /* @__PURE__ */ new Map(); + for (const [k, m] of state) { + const used = Math.floor(k / stride); + const r = k - used * stride; + if (r === 0) { + const destKey = keyOf(used, 0); + const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); + for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr); + next.set(destKey, dest); + continue; + } + const bin = binomPMF(r, pCond); + const remainingCapacity = keep - used; + for (let x = 0; x <= r; x++) { + const px = bin[x]; + if (px <= eps) continue; + const t = Math.min(x, remainingCapacity); + const used2 = used + t; + const r2 = r - x; + const add = t * v; + const destKey = keyOf(used2, r2); + const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); + for (const [sum, pr] of m) { + const s2 = sum + add; + const prob = pr * px; + const cur = dest.get(s2) || 0; + const nv = cur + prob; + if (nv >= eps) dest.set(s2, nv); + } + if (dest.size > 0) next.set(destKey, dest); + } + } + state = pruneState(next, eps * 1e-6); + processedMass += p; + } + const finalKey = keyOf(keep, 0); + const dist = state.get(finalKey) ?? /* @__PURE__ */ new Map(); + if (dist.size === 0) { + return PMF.emptyMass(); + } + const result = PMF.fromMap(dist, eps); + builderPMFCache.set(cacheKey, result); + return result; +} +function getASTSignature(node) { + switch (node.type) { + case "constant": + return `c:${node.value}`; + case "die": { + const parts = []; + parts.push(`s:${node.sides}`); + if (node.reroll) parts.push(`r:${node.reroll}`); + if (node.minimum) parts.push(`m:${node.minimum}`); + if (node.explode) parts.push(`e:${node.explode}`); + return `d{${parts.join(",")}}`; + } + case "sum": + return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`; + case "d20Roll": + return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`; + case "keep": + return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature( + node.child + )}}`; + case "half": + return `half{ch:${getASTSignature(node.child)}}`; + case "maxOf": + return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`; + case "scale": + return `scale{n:${node.numerator},d:${node.denominator},r:${node.rounding},ch:${getASTSignature(node.child)}}`; + case "add": { + let constantValue = 0; + const otherChildrenSigs = []; + for (const c of node.children) { + if (c.node.type === "constant") { + constantValue += c.sign * c.node.value; + } else { + otherChildrenSigs.push( + `${c.sign === -1 ? "-" : "+"}${getASTSignature(c.node)}` + ); + } + } + if (constantValue !== 0) { + otherChildrenSigs.push( + constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}` + ); + } + otherChildrenSigs.sort(); + return `add[${otherChildrenSigs.join("")}]`; + } + } +} + +// src/builder/attack.ts +var AttackBuilder = class _AttackBuilder { + constructor(check, hitEffect, critEffect, missEffect) { + this.check = check; + this.hitEffect = hitEffect; + this.critEffect = critEffect; + this.missEffect = missEffect; + } + onCrit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new _AttackBuilder( + this.check, + this.hitEffect, + damageRoll, + this.missEffect + ); + } + onMiss(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new _AttackBuilder( + this.check, + this.hitEffect, + this.critEffect, + damageRoll + ); + } + noCrit() { + return new _AttackBuilder(this.check, this.hitEffect, null, this.missEffect); + } + // Legacy expressions + toExpression() { + const checkPart = this.check.toExpression(); + let effectPart = ""; + if (this.hitEffect) { + effectPart = `(${this.hitEffect.toExpression()})`; + if (this.critEffect !== null) { + let crit; + if (this.critEffect) { + crit = this.critEffect; + } else { + if (this.hitEffect instanceof ParsedRollBuilder) { + crit = RollBuilder.fromArgs(0); + } else { + crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0); + } + } + const critThreshold = this.check.critThreshold; + if (critThreshold < 1 || critThreshold > 20) { + throw new Error( + `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.` + ); + } + const critExpression = crit.toExpression(); + if (critExpression !== "0") { + if (critThreshold === 20) { + effectPart += ` crit (${critExpression})`; + } else { + const xcritNumber = 21 - critThreshold; + effectPart += ` xcrit${xcritNumber} (${critExpression})`; + } + } + } + if (this.missEffect) { + effectPart += ` miss (${this.missEffect.toExpression()})`; + } + } + return `${checkPart} * ${effectPart}`; + } + resolveProbabilities(check, eps = 0) { + const rollType = check.rollType; + const rerollOne = check.baseReroll > 0; + const critThreshold = check.critThreshold; + const d202 = d20RollPMF(rollType, rerollOne); + if (check instanceof AlwaysCritBuilder) { + if (check.fromAlwaysHit) { + return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 }; + } + const ac2 = check.attackConfig.ac ?? 0; + const staticMod2 = this.check.modifier; + const bonusDicePMFs2 = this.check.getBonusDicePMFs(this.check, eps); + const bonusPMF2 = bonusDicePMFs2.length ? PMF.convolveMany(bonusDicePMFs2, eps) : PMF.delta(0, eps); + let pcrit2 = 0; + let pmiss2 = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + if (r === 1) { + pmiss2 += pr; + continue; + } + const need = ac2 - staticMod2 - r; + const pBonusHit = bonusPMF2.tailProbGE(need); + pcrit2 += pr * pBonusHit; + pmiss2 += pr * (1 - pBonusHit); + } + return { pSuccess: pcrit2, pHit: 0, pCrit: pcrit2, pMiss: pmiss2 }; + } + if (check instanceof AlwaysHitBuilder) { + let pCrit = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + if (r >= critThreshold) pCrit += pr; + } + const pHit = 1 - pCrit; + const pMiss = 0; + return { pSuccess: 1, pHit, pCrit, pMiss }; + } + const ac = check.attackConfig.ac; + const staticMod = this.check.modifier; + const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps); + const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); + let pcrit = 0; + let phit = 0; + let pmiss = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + if (r === 1) { + pmiss += pr; + continue; + } + if (r >= critThreshold) { + pcrit += pr; + continue; + } + const need = ac - staticMod - r; + const pBonusHit = bonusPMF.tailProbGE(need); + phit += pr * pBonusHit; + pmiss += pr * (1 - pBonusHit); + } + const psuccess = phit + pcrit; + return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss }; + } + resolve(eps = EPS) { + const { + pHit, + pCrit, + pMiss: pmiss + } = this.resolveProbabilities(this.check, eps); + const hitPMF = this.hitEffect ? this.hitEffect instanceof ParsedRollBuilder ? this.hitEffect.toPMF(eps) : pmfFromRollBuilder(this.hitEffect, eps) : PMF.delta(0, eps); + let critPMF = null; + let phit = pHit; + let pcrit = pCrit; + if (this.critEffect === null) { + critPMF = null; + phit += pcrit; + pcrit = 0; + } else { + let critBuilder; + if (this.critEffect) { + critBuilder = this.critEffect; + } else if (this.hitEffect instanceof ParsedRollBuilder) { + critPMF = null; + phit += pcrit; + pcrit = 0; + critBuilder = void 0; + } else { + critBuilder = this.hitEffect?.copy().doubleDice(); + } + if (critBuilder) { + critPMF = critBuilder instanceof ParsedRollBuilder ? critBuilder.toPMF(eps) : pmfFromRollBuilder(critBuilder, eps); + } + } + const missPMF = this.missEffect ? this.missEffect instanceof ParsedRollBuilder ? this.missEffect.toPMF(eps) : pmfFromRollBuilder(this.missEffect, eps) : PMF.delta(0, eps); + const mix = new Mixture(eps); + if (phit > 0) mix.add("hit", hitPMF, phit); + if (critPMF && pcrit > 0) mix.add("crit", critPMF, pcrit); + if (pmiss > 0) + mix.add(this.missEffect ? "missDamage" : "missNone", missPMF, pmiss); + return { + pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps), + check: this.check.toPMF(eps) ?? PMF.delta(0, eps), + hit: hitPMF ?? PMF.delta(0, eps), + crit: critPMF ?? PMF.delta(0, eps), + miss: missPMF ?? PMF.delta(0, eps), + weights: { hit: phit, crit: pcrit, miss: pmiss } + }; + } + // By default, create PMF with no pruning + toPMF(eps = 0) { + return this.resolve(eps).pmf; + } + get pmf() { + return this.toPMF(); + } + // By default, create query on PMF with no pruning + toQuery(eps = 0) { + return this.toPMF(eps).query(); + } +}; + +// src/builder/ac.ts +var ACBuilder = class _ACBuilder extends RollBuilder { + constructor(baseRoll, ac, attackConfig) { + super(baseRoll.getSubRollConfigs()); + if (attackConfig) { + this.attackConfig = { ...attackConfig, ac }; + } else { + this.attackConfig = { ac, critThreshold: 20 }; + } + } + onHit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new AttackBuilder(this, damageRoll); + } + get critThreshold() { + return this.attackConfig.critThreshold; + } + // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? + critOn(threshold) { + const newConfig = { + ...this.attackConfig, + critThreshold: threshold + }; + return new _ACBuilder(this, this.attackConfig.ac, newConfig); + } + alwaysCrits() { + return new AlwaysCritBuilder( + this, + { + critThreshold: this.attackConfig.critThreshold, + ac: this.attackConfig.ac + }, + false + ); + } + // Legacy expressions + toExpression() { + const configs = this.getSubRollConfigs(); + const expression = new RollBuilder(configs).toExpression(); + return this.attackConfig.ac ? `(${expression} AC ${this.attackConfig.ac})` : expression; + } + toPMF(eps = 0) { + const ac = this.attackConfig.ac; + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + const d202 = d20RollPMF(rollType, rerollOne); + const staticMod = this.modifier; + const bonusPMFs = this.getBonusDicePMFs(this, eps); + const parts = [d202, ...bonusPMFs]; + let attackRollPMF = parts.length === 1 ? d202 : PMF.convolveMany(parts, eps); + if (staticMod !== 0) + attackRollPMF = attackRollPMF.mapDamage( + (rollValue) => rollValue + staticMod + ); + const out = /* @__PURE__ */ new Map(); + for (const rollValue of attackRollPMF.support()) { + const p = attackRollPMF.pAt(rollValue); + const key = rollValue >= ac ? rollValue : 0; + out.set(key, (out.get(key) || 0) + p); + } + return PMF.fromMap(out, eps); + } + copy() { + const baseCopy = new RollBuilder(this.getSubRollConfigs()); + const newConfig = { + ac: this.attackConfig.ac, + critThreshold: this.attackConfig.critThreshold + }; + return new _ACBuilder(baseCopy, newConfig.ac, newConfig); + } +}; +RollBuilder.prototype.ac = function(targetAC) { + if (isNaN(targetAC)) throw new Error("Invalid NaN value for targetAC"); + return new ACBuilder(this, targetAC); +}; + +// src/builder/save.ts +var SaveBuilder = class _SaveBuilder { + constructor(check, failureEffect, saveOutcome = "normal") { + this.check = check; + this.failureEffect = failureEffect; + this.saveOutcome = saveOutcome; + } + saveHalf() { + return new _SaveBuilder(this.check, this.failureEffect, "half"); + } + toExpression() { + const checkPart = this.check.toExpression(); + if (!this.failureEffect) return checkPart; + const failureEffectPart = this.failureEffect.toExpression(); + const result = `${checkPart} * (${failureEffectPart})`; + return this.saveOutcome === "half" ? `${result} save half` : result; + } + resolve(eps = EPS) { + const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities( + this.check + ); + const failPMF = this.failureEffect ? this.failureEffect instanceof ParsedRollBuilder ? this.failureEffect.toPMF(eps) : pmfFromRollBuilder(this.failureEffect) : PMF.delta(0); + const onSuccess = this.saveOutcome ?? "half"; + let successPMF = PMF.delta(0, eps); + if (onSuccess === "half") successPMF = failPMF.scaleDamage(0.5, "floor"); + const successLabel = onSuccess === "normal" ? "missNone" : "saveHalf"; + const failLabel = "saveFail"; + const baseMix = new Mixture(eps); + const mixture = baseMix.add(successLabel, successPMF, psuccess).add(failLabel, failPMF, pfail); + return { + pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps), + check: PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps), + saveFail: failPMF ?? PMF.delta(0, eps), + saveSuccess: successPMF ?? PMF.delta(0, eps), + weights: { success: psuccess, fail: pfail } + }; + } + // By default, create PMF with no pruning + toPMF(eps = 0) { + return this.resolve(eps).pmf; + } + get pmf() { + return this.toPMF(); + } + // By default, create query on PMF with no pruning + toQuery(eps = 0) { + return this.toPMF(eps).query(); + } +}; +function resolveProbabilities(check) { + const saveBonus = check.modifier; + const dc = check.saveDC; + const d20Type = check.rollType; + const baseReroll = check.baseReroll; + const die = d20RollPMF(d20Type, baseReroll > 0); + const faceP = /* @__PURE__ */ new Map(); + for (const [r, bin] of die) { + const pr = bin.p; + if (pr > 0) faceP.set(r, pr); + } + const eps = 0; + const bonusDicePMFs = check.getBonusDicePMFs(check, eps); + const bonusPMF = bonusDicePMFs.length > 0 ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.zero(eps); + let pSuccess = 0; + for (let r = 1; r <= 20; r++) { + const pr = faceP.get(r); + if (!pr) continue; + const need = dc - saveBonus - r; + pSuccess += pr * bonusPMF.tailProbGE(need); + } + const pFail = Math.max(0, 1 - pSuccess); + return { pSuccess, pFail }; +} + +// src/builder/dc.ts +var DCBuilder = class _DCBuilder extends RollBuilder { + constructor(baseRoll, saveConfig) { + super(baseRoll.getSubRollConfigs()); + this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 }; + } + dc(saveDC) { + if (this.rollType && this.rollType === "elven accuracy") { + throw new Error( + "Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead." + ); + } + return new _DCBuilder(this, { dc: saveDC }); + } + get saveDC() { + return this.saveConfig.dc; + } + add(anotherRoll) { + const newBuilder = super.add(anotherRoll); + return new _DCBuilder(newBuilder, this.saveConfig); + } + addRoll(count) { + const newBuilder = super.addRoll(count); + return new _DCBuilder(newBuilder, this.saveConfig); + } + onSaveFailure(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new SaveBuilder(this, damageRoll); + } + withElvenAccuracy() { + throw new Error( + "Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks)." + ); + } + // Legacy expressions + toExpression() { + const subConfigs = this.getSubRollConfigs(); + const allConfigs = [...subConfigs]; + const expression = new RollBuilder(allConfigs).toExpression(); + return `(${expression} DC ${this.saveConfig.dc})`; + } + toPMF(eps = 0) { + const saveDC = this.saveDC; + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + const d202 = d20RollPMF(rollType, rerollOne); + const staticMod = this.modifier; + const bonusDicePMFs = this.getBonusDiceConfigs().map( + (cfg) => pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps) + ); + const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); + let psuccess = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + const need = saveDC - staticMod - r; + psuccess += pr * bonusPMF.tailProbGE(need); + } + const pfail = Math.max(0, 1 - psuccess); + const m = /* @__PURE__ */ new Map([ + [0, psuccess > 0 ? psuccess : 0], + [1, pfail > 0 ? pfail : 0] + ]); + return PMF.fromMap(m, eps); + } +}; +RollBuilder.prototype.dc = function(saveDC) { + if (isNaN(saveDC)) throw new Error("Invalid NaN value for saveDC"); + return new DCBuilder(this).dc(saveDC); +}; + +exports.ACBuilder = ACBuilder; +exports.AlwaysCritBuilder = AlwaysCritBuilder; +exports.AlwaysHitBuilder = AlwaysHitBuilder; +exports.AttackBuilder = AttackBuilder; +exports.DCBuilder = DCBuilder; +exports.HalfRollBuilder = HalfRollBuilder; +exports.MaxOfRollBuilder = MaxOfRollBuilder; +exports.ParsedRollBuilder = ParsedRollBuilder; +exports.PooledRollBuilder = PooledRollBuilder; +exports.RollBuilder = RollBuilder; +exports.SaveBuilder = SaveBuilder; +exports.ScaleRollBuilder = ScaleRollBuilder; +exports.builderPMFCache = builderPMFCache; +exports.d = d; +exports.d10 = d10; +exports.d100 = d100; +exports.d12 = d12; +exports.d20 = d20; +exports.d4 = d4; +exports.d6 = d6; +exports.d8 = d8; +exports.defaultConfig = defaultConfig; +exports.flat = flat; +exports.hd20 = hd20; +exports.roll = roll; +exports.sumRolls = sumRolls; +//# sourceMappingURL=index.cjs.map +//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/dist/builder/index.cjs.map b/dist/builder/index.cjs.map new file mode 100644 index 0000000..5bd91ca --- /dev/null +++ b/dist/builder/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/common/lru-cache.ts","../../src/common/types.ts","../../src/pmf/query.ts","../../src/pmf/pmf.ts","../../src/pmf/mixture.ts","../../src/common/errors.ts","../../src/parser/dice.ts","../../src/parser/parser.ts","../../src/builder/prob.ts","../../src/builder/d20.ts","../../src/builder/roll.ts","../../src/builder/factory.ts","../../src/builder/ast.ts","../../src/builder/attack.ts","../../src/builder/ac.ts","../../src/builder/save.ts","../../src/builder/dc.ts"],"names":["d","cumulativeSum","result","dfs","arr","d20","ac","staticMod","bonusDicePMFs","bonusPMF","pcrit","pmiss"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import { EPS, PMF } from \"../\";\n\nexport function d20PmfFromCdf(\n cdfPow: (k: number) => number,\n eps: number = EPS\n): PMF {\n const out = new Map();\n let prev = 0;\n for (let k = 1; k <= 20; k++) {\n const cur = cdfPow(k);\n const pk = cur - prev;\n if (pk > 0) {\n out.set(k, pk);\n }\n prev = cur;\n }\n\n return PMF.fromMap(out, eps);\n}\n","import { EPS, PMF, pmfCache } from \"../\";\nimport { d20PmfFromCdf } from \"./prob\";\nimport type { RollType } from \"./types\";\n\nconst cacheKeyMap: Record = {\n \"flat-flat\": \"d20\",\n \"flat-reroll\": \"hd20\",\n \"advantage-flat\": \"d20 > d20\",\n \"advantage-reroll\": \"hd20 > hd20\",\n \"disadvantage-flat\": \"d20 < d20\",\n \"disadvantage-reroll\": \"hd20 < hd20\",\n \"elven accuracy-flat\": \"d20 > d20 > d20\",\n \"elven accuracy-reroll\": \"hd20 > hd20 > hd20\",\n};\n\n/** Lift a single d20 PMF into advantage, disadvantage, or elven (triple-advantage). */\nexport function d20RollPMF(\n rollType: RollType | undefined,\n rerollOne: boolean = false\n): PMF {\n rollType = rollType || \"flat\";\n const cacheKeyLookup = `${rollType}-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cacheKey = cacheKeyMap[cacheKeyLookup];\n if (!cacheKey) {\n throw new Error(`Invalid roll type: ${rollType}`);\n }\n\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const base = d20PMF(rerollOne);\n if (!rollType || rollType === \"flat\") {\n pmfCache.set(cacheKey, base);\n return base;\n }\n\n const p: number[] = new Array(21).fill(0); // indices 1..20\n for (const [r, rec] of base) {\n const pr = typeof rec === \"number\" ? rec : rec.p;\n if (r >= 1 && r <= 20) p[r] = pr;\n }\n const F: number[] = new Array(21).fill(0);\n for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k];\n\n const eps = 0;\n let result = base;\n if (rollType === \"advantage\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps);\n } else if (rollType === \"elven accuracy\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps);\n } else if (rollType === \"disadvantage\") {\n result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps);\n }\n\n pmfCache.set(cacheKey, result);\n return result;\n}\n\nexport function d20PMF(rerollOne: boolean): PMF {\n const cacheKey = `flat-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const m = new Map();\n const base = 1 / 20;\n const rerollShare = base * base;\n if (!rerollOne) {\n for (let r = 1; r <= 20; r++) {\n m.set(r, base);\n }\n } else {\n for (let r = 1; r <= 20; r++) {\n m.set(r, (r === 1 ? 0 : base) + rerollShare);\n }\n }\n const result = PMF.fromMap(m, EPS);\n pmfCache.set(cacheKey, result);\n return result;\n}\n","import type { ACBuilder } from \"./ac\";\nimport type { CritConfig } from \"../common/types\";\nimport type { DCBuilder } from \"./dc\";\nimport { parse } from \"../parser/parser\";\nimport type { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { astFromRollConfigs, pmfFromRollBuilder } from \"./ast\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport type { ExpressionNode, KeepNode, SumNode } from \"./nodes\";\nimport type { RollConfig, RollType } from \"./types\";\n\nexport const defaultConfig: RollConfig = {\n count: 1,\n sides: 0,\n modifier: 0,\n reroll: 0,\n explode: 0,\n minimum: 0,\n bestOf: 0,\n keep: undefined,\n rollType: \"flat\",\n};\n\nconst rollConfigsEqual = (a: RollConfig, b: RollConfig) => {\n return (\n a.count === b.count &&\n a.sides === b.sides &&\n a.modifier === b.modifier &&\n a.reroll === b.reroll &&\n a.explode === b.explode &&\n a.minimum === b.minimum &&\n a.bestOf === b.bestOf &&\n a.keep === b.keep &&\n a.rollType === b.rollType\n );\n};\n\nconst configComplexityScore = (config: RollConfig) => {\n return (\n (config.reroll > 0 ? 1 : 0) +\n (config.explode > 0 ? 1 : 0) +\n (config.minimum > 0 ? 1 : 0) +\n (config.bestOf > 0 ? 1 : 0) +\n (config.keep !== undefined ? 1 : 0) +\n (config.rollType !== \"flat\" ? 1 : 0)\n );\n};\n\n// Fluent builder for dice to create PMFs with an AST\nexport class RollBuilder {\n protected readonly subRollConfigs: readonly RollConfig[];\n\n constructor(countOrConfigs: number | readonly RollConfig[] = 1) {\n if (typeof countOrConfigs === \"number\") {\n const count = countOrConfigs;\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n this.subRollConfigs = [\n { ...defaultConfig, count, isSubtraction: count < 0 },\n ];\n } else {\n this.subRollConfigs = countOrConfigs.map((c) => ({ ...c }));\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n protected get lastConfig() {\n return this.subRollConfigs[this.subRollConfigs.length - 1];\n }\n\n hasHiddenState(): boolean {\n return false;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.subRollConfigs.map((c: RollConfig) => ({ ...c }));\n }\n\n // for testing\n static fromConfig(config: Partial): RollBuilder {\n return new RollBuilder([{ ...defaultConfig, ...config }]);\n }\n\n static fromConfigs(configs: Partial[]): RollBuilder {\n return new RollBuilder(\n configs.map((config) => ({ ...defaultConfig, ...config }))\n );\n }\n\n static fromArgs(...args: any[]): RollBuilder {\n if (args.length === 1) {\n const arg = args[0];\n if (typeof arg === \"number\") {\n if (isNaN(arg)) throw new Error(\"Invalid NaN value for argument\");\n return new RollBuilder(0).plus(arg);\n }\n if (typeof arg === \"string\") {\n return new ParsedRollBuilder(arg);\n }\n if (arg instanceof RollBuilder) {\n return arg;\n }\n }\n\n if (args.length === 2 || args.length === 3) {\n const [count, sidesOrDie, modifier] = args;\n\n if (typeof count !== \"number\") {\n throw new Error(\"First argument must be a number for multi-arg call\");\n }\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count argument\");\n\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) {\n const result = new RollBuilder(0);\n return modifier !== undefined ? result.plus(modifier) : result;\n }\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return modifier !== undefined\n ? resultBuilder.plus(modifier)\n : resultBuilder;\n } else if (typeof sidesOrDie === \"number\" || sidesOrDie === undefined) {\n if (typeof sidesOrDie === \"number\" && isNaN(sidesOrDie))\n throw new Error(\"Invalid NaN value for sides argument\");\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return modifier !== undefined ? builder.plus(modifier) : builder;\n }\n }\n\n throw new Error(`Invalid arguments passed: ${args.join(\", \")}`);\n }\n\n // --- Core Dice Methods ---\n d(sides: number | undefined): RollBuilder {\n if (sides !== undefined && isNaN(sides))\n throw new Error(\"Invalid NaN value for sides\");\n if (sides === undefined) return this;\n if (this.lastConfig.sides && this.lastConfig.sides > 0) {\n throw new Error(\"Cannot add a die after adding a die\");\n }\n if (sides === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].sides = sides;\n return this.create(newConfigs);\n }\n\n plus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n plus(count: number, die: RollBuilder): RollBuilder;\n plus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n if (typeof modOrRoll === \"number\" && isNaN(modOrRoll))\n throw new Error(\"Invalid NaN value for modOrRoll\");\n if (die instanceof RollBuilder && typeof modOrRoll === \"number\") {\n if (die.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const count = modOrRoll;\n const subRollConfigs = die.getSubRollConfigs();\n if (subRollConfigs.length === 0) return this;\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let rollToAdd = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = rollToAdd\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n rollToAdd = new RollBuilder(negatedConfigs);\n }\n return this.add(rollToAdd);\n }\n\n if (die !== undefined) {\n throw new Error(\"Invalid arguments to plus()\");\n }\n\n if (modOrRoll === undefined) return this;\n if (typeof modOrRoll === \"number\") {\n if (modOrRoll === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].modifier += modOrRoll;\n return this.create(newConfigs);\n }\n return this.add(modOrRoll as RollBuilder);\n }\n\n minus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n minus(count: number, die: RollBuilder): RollBuilder;\n minus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n const isNumber = typeof modOrRoll === \"number\";\n const dieIsRoll = die instanceof RollBuilder;\n if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die);\n\n if (die !== undefined) throw new Error(\"Invalid arguments to minus()\");\n if (modOrRoll === undefined) return this;\n\n return isNumber\n ? this.plus(-modOrRoll)\n : this.plus(-1, modOrRoll as RollBuilder);\n }\n\n /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */\n reroll(value: number): RollBuilder {\n if (isNaN(value)) throw new Error(\"Invalid NaN value for reroll\");\n if (value === this.lastConfig.reroll) return this;\n\n const newConfigs = this.getSubRollConfigs();\n\n newConfigs[newConfigs.length - 1].reroll = value;\n return this.create(newConfigs);\n }\n\n /** Set finite explode count for max-face explosions (Infinity allowed). */\n explode(count: number | undefined = Infinity): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for explode count\");\n if (count === undefined) return this;\n if (count === 0) return this;\n if (count < 0) throw new Error(\"Explode count must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].explode = count;\n return this.create(newConfigs);\n }\n\n /** Apply per-die minimum value (min > 0). */\n minimum(val: number | undefined): RollBuilder {\n if (val !== undefined && isNaN(val))\n throw new Error(\"Invalid NaN value for minimum\");\n if (val === undefined) return this;\n if (val === 0) return this;\n if (val < 0) throw new Error(\"Minimum value must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].minimum = val + 1;\n return this.create(newConfigs);\n }\n\n bestOf(count: number | undefined): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for bestOf count\");\n if (count === undefined) return this;\n if (count <= 0) throw new Error(\"Best of count must be > 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].bestOf = count;\n return this.create(newConfigs);\n }\n\n keepHighest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"highest\" };\n return this.create(newConfigs);\n }\n\n keepLowest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"lowest\" };\n return this.create(newConfigs);\n }\n\n keepHighestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighestAll\");\n const currentAST = this.toAST();\n // Wrap in SumNode to represent trials, then KeepNode\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"highest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kh${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n keepLowestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowestAll\");\n const currentAST = this.toAST();\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"lowest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kl${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n withAdvantage(): RollBuilder {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"advantage\";\n return this.create(newConfigs);\n }\n\n withDisadvantage(): RollBuilder {\n const configs = this.getSubRollConfigs();\n configs[configs.length - 1].rollType = \"disadvantage\";\n return this.create(configs);\n }\n\n add(anotherRoll: RollBuilder | undefined): RollBuilder {\n if (anotherRoll === undefined) return this;\n if (anotherRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll).\"\n );\n }\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n withBonus(anotherRoll: RollBuilder): RollBuilder {\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n addRoll(count: number = 1): RollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n const configs = [\n ...this.subRollConfigs,\n {\n ...defaultConfig,\n count,\n isSubtraction: count < 0,\n },\n ];\n return this.create(configs);\n }\n\n scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n const newConfigs = this.getSubRollConfigs().map((config) => {\n if (!config.sides || config.sides <= 0) return config;\n return { ...config, count: config.count * scaleInt };\n });\n return this.create(newConfigs);\n }\n\n doubleDice(): RollBuilder {\n return this.scaleDice(2);\n }\n\n alwaysHits() {\n return new AlwaysHitBuilder(this);\n }\n\n alwaysCrits() {\n return new AlwaysCritBuilder(this);\n }\n\n copy(): RollBuilder {\n return this.create(this.getSubRollConfigs());\n }\n\n // --- Dice Shortcut Methods ---\n d4 = () => this.d(4);\n d6 = () => this.d(6);\n d8 = () => this.d(8);\n d10 = () => this.d(10);\n d12 = () => this.d(12);\n d20 = () => this.d(20);\n d100 = () => this.d(100);\n\n withElvenAccuracy() {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"elven accuracy\";\n return this.create(newConfigs);\n }\n\n toExpression(): string {\n const originalDiceConfigs = this.subRollConfigs.filter(\n (config) => config.sides && config.sides > 0\n );\n\n type Group = { config: RollConfig; totalCount: number };\n const configGroups = new Map();\n\n for (const config of originalDiceConfigs) {\n const keyConfig: Partial = { ...config };\n delete keyConfig.count;\n delete keyConfig.modifier;\n const key = JSON.stringify(keyConfig);\n\n const existingGroup = configGroups.get(key);\n if (existingGroup) {\n existingGroup.totalCount += config.count;\n } else {\n configGroups.set(key, { config, totalCount: config.count });\n }\n }\n\n const rootConfig = this.getRootDieConfig();\n const groupedConfigs = Array.from(configGroups.values());\n let rootD20Group: Group | undefined;\n\n if (rootConfig && rootConfig.sides === 20) {\n const rootIndex = groupedConfigs.findIndex(\n ({ config }) =>\n rollConfigsEqual(config, rootConfig) &&\n JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep)\n );\n\n if (rootIndex !== -1) {\n rootD20Group = groupedConfigs.splice(rootIndex, 1)[0];\n }\n }\n\n const sortedDiceConfigs = groupedConfigs\n .map(({ config, totalCount }) => ({\n ...config,\n count: totalCount,\n }))\n .sort((a, b) => {\n const aHasPriority = a.reroll > 0 || a.minimum > 0;\n const bHasPriority = b.reroll > 0 || b.minimum > 0;\n if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1;\n if (b.sides !== a.sides) return b.sides - a.sides;\n return configComplexityScore(b) - configComplexityScore(a);\n });\n\n const diceConfigs = rootD20Group\n ? [\n { ...rootD20Group.config, count: rootD20Group.totalCount },\n ...sortedDiceConfigs,\n ]\n : sortedDiceConfigs;\n\n const totalModifier = this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n if (diceConfigs.length === 0) return totalModifier.toString();\n\n const rootDieConfig = this.getRootDieConfig();\n const newRootConfig = rootDieConfig\n ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig))\n : undefined;\n\n // Generate dice expressions without individual modifiers\n const diceExpressions = diceConfigs.map((config) =>\n this.configToSingleExpressionWithoutModifier(\n config,\n config === newRootConfig\n )\n );\n\n // Join dice expressions with appropriate operators based on their count\n let result = \"\";\n for (let i = 0; i < diceExpressions.length; i++) {\n const config = diceConfigs[i];\n const expression = diceExpressions[i];\n\n if (i === 0) {\n result = (config.isSubtraction ? \"-\" : \"\") + expression;\n\n // Add constants right after the root d20 die (if it's a d20)\n if (config.sides === 20 && totalModifier !== 0) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else result += ` - ${Math.abs(totalModifier)}`;\n }\n } else {\n // Use minus sign for negative subtraction, plus sign otherwise\n const operator = config.isSubtraction ? \" - \" : \" + \";\n result += operator + expression;\n }\n }\n\n // If constants weren't added after d20, add them at the end\n if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`;\n }\n\n return result.replace(/\\+ -/g, \"-\");\n }\n\n toPMF(eps: number = 0): PMF {\n // Main AST entry point\n return pmfFromRollBuilder(this, eps);\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n\n toAST(): ExpressionNode {\n const configs = this.getSubRollConfigs();\n return (\n astFromRollConfigs(configs) ||\n ({ type: \"constant\", value: 0 } as ExpressionNode)\n );\n }\n\n private configToSingleExpressionWithoutModifier(\n config: RollConfig,\n isRootDie: boolean\n ): string {\n if (!config.sides || config.sides <= 0) return \"\";\n\n let baseDie = `d${config.sides}`;\n\n if (config.reroll > 0) {\n if (config.minimum > 0 && config.explode > 0) {\n // Complex single roll case: minimum + explode + reroll\n // Apply reroll after minimum is applied\n } else if (config.minimum > 0) {\n // When there's a minimum but no explode, use descending order and apply before minimum\n for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`;\n } else {\n // When there's no minimum, use ascending order\n for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`;\n }\n }\n\n if (config.minimum > 0) {\n if (config.reroll > 0 && !config.explode) {\n baseDie = `${config.minimum}>(${baseDie})`;\n } else {\n baseDie = `${config.minimum}>${baseDie}`;\n }\n if (config.reroll > 0 && config.explode > 0) {\n for (let i = 1; i <= config.reroll; i++) {\n baseDie += ` reroll ${i}`;\n }\n }\n }\n\n // Check for hd20 shorthand AFTER adding explode\n if (baseDie === \"d20 reroll 1\" && config.minimum <= 1) baseDie = \"hd20\";\n\n let mainExpression = \"\";\n switch (config.rollType) {\n case \"advantage\":\n mainExpression = `${baseDie} > ${baseDie}`;\n break;\n case \"disadvantage\":\n mainExpression = `${baseDie} < ${baseDie}`;\n break;\n case \"elven accuracy\":\n mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`;\n break;\n case \"flat\":\n if (config.keep) {\n const mode = config.keep.mode === \"highest\" ? \"kh\" : \"kl\";\n const baseDieExpression =\n this.configToSingleExpressionWithoutModifier(\n {\n ...config,\n count: config.count,\n modifier: 0,\n rollType: \"flat\",\n keep: undefined,\n },\n false\n );\n mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`;\n } else {\n const isComplex = baseDie.length > `d${config.sides}`.length;\n const isHalflingShorthand = baseDie === \"hd20\";\n const isD20Shorthand = baseDie === \"d20\" && isRootDie;\n const hasMinimum = config.minimum > 0;\n const hasReroll = config.reroll > 0;\n // For negative subtraction, use absolute value for display\n // For negative counts from factory function, treat as 1 (legacy behavior)\n const effectiveCount = config.isSubtraction\n ? Math.abs(config.count)\n : config.count < 0\n ? 1\n : Math.abs(config.count);\n\n if (effectiveCount > 1) {\n const shouldAddParentheses = isComplex;\n mainExpression = shouldAddParentheses\n ? `${effectiveCount}(${baseDie})`\n : `${effectiveCount}${baseDie}`;\n } else if (effectiveCount === 1) {\n const needsParens = hasReroll && hasMinimum;\n if (config.isSubtraction) {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n } else if (\n isComplex ||\n isHalflingShorthand ||\n isD20Shorthand ||\n config.count < 0\n ) {\n mainExpression = needsParens ? `1(${baseDie})` : baseDie;\n } else {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n }\n } else {\n mainExpression = baseDie;\n }\n }\n if (config.bestOf && config.count && config.bestOf < config.count) {\n mainExpression += `kh${config.bestOf}`;\n }\n break;\n }\n\n return mainExpression;\n }\n\n getRootDieConfig(): RollConfig | undefined {\n const configs = this.subRollConfigs;\n return configs.find((config) => config.sides > 0) || configs[0];\n }\n\n getAllDieConfigs(): readonly RollConfig[] {\n return this.getSubRollConfigs();\n }\n\n getBonusDiceConfigs(): RollConfig[] {\n const allConfigs = this.subRollConfigs;\n const rootConfig =\n allConfigs.find((config) => config.sides > 0) || allConfigs[0];\n if (!rootConfig) return [];\n return allConfigs\n .filter((config) => config.sides > 0)\n .filter((config) => config !== rootConfig);\n }\n\n getBonusDicePMFs(check: RollBuilder, eps: number = 0): PMF[] {\n return check\n .getBonusDiceConfigs()\n .map((config) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([config]), eps)\n );\n }\n\n get modifier(): number {\n return this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n }\n\n get rollType(): RollType {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.rollType || \"flat\";\n }\n\n get baseReroll(): number {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.reroll || 0;\n }\n\n half(): HalfRollBuilder {\n return new HalfRollBuilder(this);\n }\n\n /**\n * Scale this roll's result by `numerator / denominator`, rounding each outcome.\n * A general, composable form of {@link half} — used to model damage-type resistance\n * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`).\n * Compose several of these (and plain rolls) into one payload with {@link sumRolls}.\n */\n scaleResult(\n numerator: number,\n denominator: number = 1,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): ScaleRollBuilder {\n return new ScaleRollBuilder(this, numerator, denominator, rounding);\n }\n\n // Create a \"max of N rolls\" version of this roll for crit damage with keep operations\n maxOf(count: number): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this, count);\n }\n\n // These methods are implemented via prototype augmentation in ac.ts and dc.ts\n // They are declared here to provide proper TypeScript types\n ac(_targetAC: number): ACBuilder {\n throw new Error(\"ac() should be implemented via prototype augmentation\");\n }\n\n dc(_saveDC: number): DCBuilder {\n throw new Error(\"dc() should be implemented via prototype augmentation\");\n }\n}\n\nexport class HalfRollBuilder extends RollBuilder {\n constructor(private readonly innerRoll: RollBuilder) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n // No need to override create if we don't expose RollBuilder methods that use it,\n // but HalfRollBuilder extends RollBuilder so it does.\n // However, HalfRollBuilder seems to just wrap another roll.\n // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder?\n // No, RollBuilder.plus returns RollBuilder.\n // The inheritance here is a bit tricky.\n // Existing code for HalfRollBuilder doesn't seem to implement plus/etc.\n // So .plus() on a HalfRollBuilder would return a RollBuilder (base class).\n // Which is fine.\n // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that.\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const innerExpression = this.innerRoll.toExpression();\n return `(${innerExpression}) // 2`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"half\",\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): HalfRollBuilder {\n return new HalfRollBuilder(this.innerRoll.copy());\n }\n}\n\n/**\n * A roll whose result is scaled by `numerator / denominator` and rounded — the composable\n * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or\n * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls.\n */\nexport class ScaleRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly numerator: number,\n private readonly denominator: number = 1,\n private readonly rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const inner = this.innerRoll.toExpression();\n if (this.denominator === 1) return `${this.numerator} * (${inner})`;\n if (this.numerator === 1) return `(${inner}) // ${this.denominator}`;\n return `(${inner}) * ${this.numerator} // ${this.denominator}`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"scale\",\n numerator: this.numerator,\n denominator: this.denominator,\n rounding: this.rounding,\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): ScaleRollBuilder {\n return new ScaleRollBuilder(\n this.innerRoll.copy(),\n this.numerator,\n this.denominator,\n this.rounding\n );\n }\n}\n\nexport class MaxOfRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly count: number,\n private readonly diceCount?: number,\n private readonly diceSides?: number\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n // Use the stored dice info to create the expression directly\n if (this.diceCount && this.diceSides) {\n return `max${this.count}(${this.diceCount}d${this.diceSides})`;\n }\n\n // If no stored dice info, fallback to simple max expression\n return `max${this.count}(?d?)`;\n }\n\n toAST(): ExpressionNode {\n // Use the stored dice info if available\n if (this.diceCount && this.diceSides) {\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: this.diceCount,\n child: { type: \"die\", sides: this.diceSides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n\n // Fallback: try to get from innerRoll\n try {\n const configs = this.innerRoll.getSubRollConfigs();\n if (configs.length === 1 && configs[0].sides) {\n const config = configs[0];\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: config.count,\n child: { type: \"die\", sides: config.sides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n } catch {\n // Last resort: try parsing the expression (though this shouldn't work with current RollBuilder)\n }\n\n // Fallback - this shouldn't happen in normal usage\n throw new Error(\n `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`\n );\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this.innerRoll.copy(), this.count);\n }\n}\n\nexport class AlwaysHitBuilder extends RollBuilder {\n readonly attackConfig: CritConfig;\n\n constructor(baseRoll: RollBuilder, attackConfig?: CritConfig) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysHitBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(critThreshold: number): AlwaysHitBuilder {\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(this, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(this, undefined, true);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysHitBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(baseCopy, newConfig);\n }\n}\n\nexport class AlwaysCritBuilder extends RollBuilder {\n readonly attackConfig: CritConfig & { ac?: number };\n readonly fromAlwaysHit: boolean;\n\n constructor(\n baseRoll: RollBuilder,\n attackConfig?: CritConfig & { ac?: number },\n fromAlwaysHit: boolean = false\n ) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysCritBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n critOn(critThreshold: number): AlwaysCritBuilder {\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysCritBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit);\n }\n}\n\nexport class ParsedRollBuilder extends RollBuilder {\n private readonly cachedPMF: PMF;\n private readonly originalExpression: string;\n\n constructor(expression: string) {\n super([]); // Empty configs since we're bypassing the normal builder flow\n this.originalExpression = expression;\n this.cachedPMF = parse(expression, 0);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n override toPMF(_eps: number = 0): PMF {\n // Return the pre-computed PMF, ignoring epsilon for now\n // The parse() function was already called with eps=0\n return this.cachedPMF;\n }\n\n override toExpression(): string {\n return this.originalExpression;\n }\n\n override toAST(): ExpressionNode {\n // Since we don't have the actual AST structure, return a constant node\n // This is a limitation but shouldn't matter for terminal damage expressions\n throw new Error(\n \"ParsedRollBuilder does not support AST conversion. Use the builder API instead.\"\n );\n }\n\n override copy(): ParsedRollBuilder {\n return new ParsedRollBuilder(this.originalExpression);\n }\n\n override doubleDice(): ParsedRollBuilder {\n throw new Error(\n \"ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead.\"\n );\n }\n}\n\nexport class PooledRollBuilder extends RollBuilder {\n constructor(\n private readonly baseAST: ExpressionNode,\n private readonly baseExpression: string,\n configs: readonly RollConfig[] = []\n ) {\n // Initialize with empty config if none provided\n super(configs.length > 0 ? configs : 0);\n }\n\n protected create(configs: readonly RollConfig[]): PooledRollBuilder {\n // This is the key fix: we preserve the baseAST and baseExpression\n // and only update the configs\n return new PooledRollBuilder(this.baseAST, this.baseExpression, configs);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override d(_sides: number | undefined): RollBuilder {\n throw new Error(\"Cannot add dice to a pooled roll. The pool is finalized.\");\n }\n\n override reroll(_value: number): RollBuilder {\n throw new Error(\"Cannot set reroll on a pooled roll.\");\n }\n\n override explode(_count: number | undefined = Infinity): RollBuilder {\n throw new Error(\"Cannot set explode on a pooled roll.\");\n }\n\n override minimum(_val: number | undefined): RollBuilder {\n throw new Error(\"Cannot set minimum on a pooled roll.\");\n }\n\n override bestOf(_count: number | undefined): RollBuilder {\n throw new Error(\"Cannot set bestOf on a pooled roll.\");\n }\n\n override keepHighest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling.\"\n );\n }\n\n override keepLowest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling.\"\n );\n }\n\n override withAdvantage(): RollBuilder {\n throw new Error(\"Cannot set advantage on a pooled roll.\");\n }\n\n override withDisadvantage(): RollBuilder {\n throw new Error(\"Cannot set disadvantage on a pooled roll.\");\n }\n\n override withElvenAccuracy(): RollBuilder {\n throw new Error(\"Cannot set elven accuracy on a pooled roll.\");\n }\n\n override toAST(): ExpressionNode {\n const configsAST = super.toAST();\n\n // Check if configsAST is effectively zero/empty\n const isZero = configsAST.type === \"constant\" && configsAST.value === 0;\n\n if (isZero) {\n return this.baseAST;\n }\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [\n { node: this.baseAST, sign: 1 },\n { node: configsAST, sign: 1 },\n ];\n\n return { type: \"add\", children };\n }\n\n override toExpression(): string {\n const configsExpression = super.toExpression();\n\n // If no configs added, just return base expression\n if (configsExpression === \"0\") {\n return this.baseExpression;\n }\n\n // Clean up the join\n if (configsExpression.startsWith(\"-\")) {\n // If it's a negative number/expression, format as \" - value\"\n // configsExpression is like \"-2\" or \"-1d6\"\n return `${this.baseExpression} - ${configsExpression.substring(1)}`;\n }\n return `${this.baseExpression} + ${configsExpression}`;\n }\n\n override copy(): PooledRollBuilder {\n return new PooledRollBuilder(\n this.baseAST,\n this.baseExpression,\n this.getSubRollConfigs()\n );\n }\n\n override scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n // Scale the base pool (treat it as a die/unit)\n // We wrap the base AST in a SumNode\n const newBaseAST: SumNode = {\n type: \"sum\",\n count: scaleInt,\n child: this.baseAST,\n };\n const newBaseExpr =\n scaleInt === 1\n ? this.baseExpression\n : `${scaleInt}(${this.baseExpression})`;\n\n // We preserve the existing modifiers (subRollConfigs) without scaling them,\n // because scaleDice() generally only scales \"dice\", not flat modifiers.\n // Since we forbid adding dice to PooledRollBuilder, subRollConfigs are only modifiers.\n return new PooledRollBuilder(\n newBaseAST,\n newBaseExpr,\n this.getSubRollConfigs()\n );\n }\n\n times(count: number): PooledRollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for times\");\n if (Math.floor(count) !== count)\n throw new Error(\"times() requires an integer\");\n if (count < 0) throw new Error(\"times() requires a non-negative integer\");\n\n // We wrap the current state (base + modifiers) into a new pool repeated N times\n const currentAST = this.toAST();\n const currentExpr = this.toExpression();\n\n const sumNode: SumNode = {\n type: \"sum\",\n count,\n child: currentAST,\n };\n\n const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`;\n\n return new PooledRollBuilder(sumNode, newExpr);\n }\n}\n\n/**\n * An additive composite of independent rolls that preserves each part's AST — the piece\n * that lets a scaled/halved sub-roll (which the flat `.plus()` merge would otherwise drop)\n * sit beside plain rolls in one damage payload. Its PMF convolves the parts; its expression\n * joins them with ` + `. Built via {@link sumRolls}; terminal (used as an onHit/onCrit/\n * onSaveFailure payload), so it reports hidden state to reject accidental flat merges.\n */\nclass CompositeSumRollBuilder extends RollBuilder {\n constructor(private readonly parts: readonly RollBuilder[]) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override getSubRollConfigs(): readonly RollConfig[] {\n return [];\n }\n\n override toAST(): ExpressionNode {\n return {\n type: \"add\",\n children: this.parts.map((p) => ({\n node: p.toAST(),\n sign: 1 as const,\n })),\n };\n }\n\n override toExpression(): string {\n const exprs = this.parts\n .map((p) => p.toExpression())\n .filter((e) => e && e !== \"0\");\n if (exprs.length === 0) return \"0\";\n let result = exprs[0];\n for (let i = 1; i < exprs.length; i++) {\n const e = exprs[i];\n result += e.startsWith(\"-\") ? ` - ${e.substring(1)}` : ` + ${e}`;\n }\n return result.replace(/\\+ -/g, \"-\");\n }\n\n override toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n override copy(): CompositeSumRollBuilder {\n return new CompositeSumRollBuilder(this.parts.map((p) => p.copy()));\n }\n}\n\n/**\n * Combine several rolls into one additive payload whose PMF is their convolution and whose\n * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry\n * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance\n * and vulnerability survive into both the distribution and the rendered expression.\n * Empty parts collapse to `0`; a single part is returned unwrapped.\n */\nexport function sumRolls(parts: readonly RollBuilder[]): RollBuilder {\n const meaningful = parts.filter((p): p is RollBuilder => p !== undefined);\n if (meaningful.length === 0) return new RollBuilder(0);\n if (meaningful.length === 1) return meaningful[0];\n return new CompositeSumRollBuilder(meaningful);\n}\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { RollBuilder } from \"./roll\";\nimport type { RollFactory } from \"./types\";\n\nconst rollFn = (\n count: number,\n sidesOrDie?: number | RollBuilder,\n modifier?: number\n): RollBuilder => {\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n // roll(2, d6, 5)\n // Create a new config, using the base die's config but overriding the count\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier);\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return resultBuilder.plus(modifier);\n } else {\n // roll(2, 6, 5)\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return builder.plus(modifier);\n }\n};\n\nrollFn.d = (sides: number | string): RollBuilder => {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n};\nrollFn.hd20 = (): RollBuilder => new RollBuilder(1).d20().reroll(1);\nrollFn.d4 = (): RollBuilder => new RollBuilder(1).d4();\nrollFn.d6 = (): RollBuilder => new RollBuilder(1).d6();\nrollFn.d8 = (): RollBuilder => new RollBuilder(1).d8();\nrollFn.d10 = (): RollBuilder => new RollBuilder(1).d10();\nrollFn.d12 = (): RollBuilder => new RollBuilder(1).d12();\nrollFn.d20 = (): RollBuilder => new RollBuilder(1).d20();\nrollFn.d100 = (): RollBuilder => new RollBuilder(1).d100();\nrollFn.flat = (n: number): RollBuilder => new RollBuilder(0).plus(n);\n\nexport function d(sides: number | string): RollBuilder {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n}\n\nexport const d4 = new RollBuilder(1).d4();\nexport const d6 = new RollBuilder(1).d6();\nexport const d8 = new RollBuilder(1).d8();\nexport const d10 = new RollBuilder(1).d10();\nexport const d12 = new RollBuilder(1).d12();\nexport const d20 = new RollBuilder(1).d20();\nexport const hd20 = new RollBuilder(1).d20().reroll(1);\nexport const d100 = new RollBuilder(1).d100();\nexport const flat = (n: number) => new RollBuilder(0).plus(n);\n\nexport const roll: RollFactory = rollFn as RollFactory;\n\nexport const builderPMFCache = new LRUCache(1000);\n","import { LRUCache, PMF } from \"../\";\nimport { d20RollPMF } from \"./d20\";\nimport { builderPMFCache } from \"./factory\";\nimport type {\n AddNode,\n ConstantNode,\n D20RollNode,\n DieNode,\n ExpressionNode,\n KeepNode,\n MaxOfNode,\n SumNode,\n} from \"./nodes\";\nimport type { RollBuilder } from \"./roll\";\nimport type { RollConfig } from \"./types\";\n\n// For now, default to 0 epsilon. Later we can tighten to EPS.\nconst defaultEps = 0;\n\nconst singleDiePMFCache = new LRUCache(1000);\n\nexport function astFromRollConfigs(\n configs: readonly RollConfig[]\n): ExpressionNode | undefined {\n // TODO add cache for this\n if (!configs || configs.length === 0) return undefined;\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [];\n let constantSum = 0;\n\n for (const cfg of configs) {\n const sign: 1 | -1 = cfg.isSubtraction || cfg.count < 0 ? -1 : 1;\n const count = Math.abs(cfg.count || 0);\n\n constantSum += cfg.modifier || 0;\n\n if ((cfg.sides || 0) <= 0) continue;\n\n const die: DieNode = {\n type: \"die\",\n sides: cfg.sides,\n reroll: cfg.reroll > 0 ? cfg.reroll : undefined,\n minimum: cfg.minimum > 0 ? cfg.minimum : undefined,\n explode:\n cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0\n ? cfg.explode\n : undefined,\n };\n\n let node: ExpressionNode = die;\n\n let appliedRollType = false;\n if (cfg.rollType && cfg.rollType !== \"flat\") {\n if (cfg.sides === 20) {\n node = {\n type: \"d20Roll\",\n rollType: cfg.rollType,\n child: node,\n } as D20RollNode;\n } else {\n const n = cfg.rollType === \"elven accuracy\" ? 3 : 2;\n const mode = cfg.rollType === \"disadvantage\" ? \"lowest\" : \"highest\";\n const base: SumNode = { type: \"sum\", count: n, child: node };\n node = { type: \"keep\", mode, count: 1, child: base } as KeepNode;\n }\n appliedRollType = true;\n }\n\n if (cfg.rollType === \"flat\" && cfg.keep && cfg.keep.total > 0) {\n const baseCount = Math.max(1, Math.floor(Math.abs(count || 1)));\n const trials = Math.max(1, Math.floor(cfg.keep.total));\n const k = Math.max(0, Math.floor(cfg.keep.count));\n\n // For keep-highest of 1, always treat as trials-of-sums: max over trial sums\n if (k === 1 && cfg.keep.mode === \"highest\") {\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n node = {\n type: \"maxOf\",\n count: trials,\n child: perTrial,\n } as MaxOfNode;\n }\n } else if (trials === baseCount) {\n // Classic pool: keep K of N faces from N iid dice\n const base: SumNode = { type: \"sum\", count: trials, child: node };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: base,\n } as KeepNode;\n } else {\n // General trials-of-sums: trials of (baseCount dice sum), keep K trial sums\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n const trialPool: SumNode = {\n type: \"sum\",\n count: trials,\n child: perTrial,\n };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: trialPool,\n } as KeepNode;\n }\n }\n } else {\n const c = appliedRollType ? 1 : Math.max(1, count || 1);\n node = { type: \"sum\", count: c, child: node } as SumNode;\n }\n\n children.push({ node, sign });\n }\n\n if (children.length === 0) {\n return { type: \"constant\", value: constantSum } as ConstantNode;\n }\n\n const add: AddNode = { type: \"add\", children };\n if (constantSum !== 0)\n add.children.push({\n node: { type: \"constant\", value: constantSum },\n sign: 1,\n });\n return add;\n}\n\nexport function resolve(node: ExpressionNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(node);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n const result = ((): PMF => {\n switch (node.type) {\n case \"constant\":\n return PMF.delta(node.value, eps);\n\n case \"die\": {\n return resolveSingleDie(node, eps);\n }\n\n case \"sum\": {\n const base = resolve(node.child, eps);\n const n = Math.max(0, Math.floor(node.count));\n if (n === 0) return PMF.delta(0, eps);\n if (n === 1) return base;\n return base.power(n, eps);\n }\n\n case \"add\": {\n let shift = 0;\n const parts: PMF[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n shift += c.sign * c.node.value;\n } else {\n const p = resolve(c.node, eps);\n parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v));\n }\n }\n if (parts.length === 0) return PMF.delta(shift, eps);\n let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps);\n if (shift !== 0) res = res.mapDamage((v) => v + shift);\n return res;\n }\n\n case \"keep\": {\n const totalTrials = getTotalCount(node);\n const keepCount = Math.max(0, Math.min(node.count, totalTrials));\n if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps);\n\n // Resolve the per-trial PMF (the child of the Sum inside Keep)\n const perTrialNode = node.child.child; // Sum(child: perTrial)\n const perTrialPMF = resolve(perTrialNode, eps);\n\n return keepSumPMF(\n perTrialPMF,\n totalTrials,\n keepCount,\n node.mode === \"highest\",\n eps\n );\n }\n\n case \"d20Roll\": {\n const childDie = findDie(node.child);\n const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;\n return d20RollPMF(node.rollType, rerollOne);\n }\n\n case \"half\": {\n const childPMF = resolve(node.child, eps);\n return childPMF.scaleDamage(0.5, \"floor\");\n }\n\n case \"maxOf\": {\n const childPMF = resolve(node.child, eps);\n const count = Math.max(1, Math.floor(node.count));\n if (count === 1) return childPMF;\n\n // Compute the maximum of count independent rolls of childPMF\n return computeMaxOfPMF(childPMF, count, eps);\n }\n\n case \"scale\": {\n const childPMF = resolve(node.child, eps);\n const denom = node.denominator === 0 ? 1 : node.denominator;\n return childPMF.scaleDamage(node.numerator / denom, node.rounding);\n }\n }\n })();\n\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function pmfFromRollBuilder(\n rb: RollBuilder,\n eps: number = defaultEps\n): PMF {\n const ast = rb.toAST();\n return resolve(ast, eps);\n}\n\nfunction resolveSingleDie(die: DieNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(die);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = singleDiePMFCache.get(cacheKey);\n if (cached) return cached;\n\n const s = Math.max(0, Math.floor(die.sides));\n if (s <= 0) return PMF.delta(0, eps);\n\n let probs = new Map();\n for (let v = 1; v <= s; v++) probs.set(v, 1 / s);\n\n // TODO - check if this is correct. Sequential reroll passes? Or at once?\n const r = Math.max(0, Math.floor(die.reroll || 0));\n if (r > 0) {\n const k = Math.min(r, s);\n const rerollMass = k / s; // total probability rerolled once\n const uniformReroll = rerollMass / s; // mass added to each face from reroll\n const next = new Map();\n for (let v = 1; v <= s; v++) {\n const keep = v <= k ? 0 : 1 / s;\n next.set(v, keep + uniformReroll);\n }\n probs = next;\n }\n\n let pmf = PMF.fromMap(new Map(probs), eps);\n\n // Minimum per die\n const minV = Math.max(0, Math.floor(die.minimum || 0));\n if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV));\n\n // Exploding dice (finite) on max face only\n const explode = die.explode;\n if (explode && Number.isFinite(explode) && explode > 0) {\n const times = Math.floor(explode);\n const maxFace = s;\n\n // Split pmf into non-max and max\n const nonMax = new Map();\n const pMax = pmf.pAt(maxFace);\n for (const v of pmf.support()) {\n if (v !== maxFace) nonMax.set(v, pmf.pAt(v));\n }\n let nonMaxPMF = PMF.fromMap(nonMax, eps);\n if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) {\n // keep raw mass composition\n nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax);\n }\n\n // TODO - explosions\n // Additional roll distribution equals original pmf without explosions applied again.\n // For simplicity we treat cascaded explosions as adding uniform max-only triggers.\n // Compute sum of up to `times` additional rolls conditioned on each explosion hit.\n let tail = PMF.delta(0, eps);\n const addOnce = pmf;\n for (let t = 1; t <= times; t++) {\n tail = tail.convolve(addOnce, eps);\n }\n // Mix: with prob (1 - pMax) take nonMax, with prob pMax take maxFace + tail\n const exploded = PMF.branch(\n tail.mapDamage((v) => v + maxFace),\n nonMaxPMF,\n pMax\n );\n pmf = exploded;\n }\n\n singleDiePMFCache.set(cacheKey, pmf);\n return pmf;\n}\n\n// Getters\n\nfunction findDie(node: ExpressionNode): DieNode | undefined {\n switch (node.type) {\n case \"die\":\n return node;\n case \"constant\":\n return undefined;\n case \"sum\":\n case \"d20Roll\":\n case \"half\":\n case \"maxOf\":\n case \"scale\":\n return findDie(node.child);\n case \"keep\":\n return findDie(node.child.child);\n case \"add\":\n for (const c of node.children) {\n const d = findDie(c.node);\n if (d) return d;\n }\n return undefined;\n }\n}\n\nfunction getTotalCount(node: KeepNode): number {\n // The total dice count is encoded in the nearest SumNode under child\n let cur = node.child;\n while (cur.type === \"keep\") cur = cur.child;\n return cur.type === \"sum\" ? Math.max(0, Math.floor(cur.count)) : 0;\n}\n\nfunction computeMaxOfPMF(\n pmf: PMF,\n count: number,\n eps: number = defaultEps\n): PMF {\n // Compute the maximum of 'count' independent rolls of the given PMF\n if (count <= 1) return pmf;\n\n const support = pmf.support();\n const out = new Map();\n\n // For small counts, we can enumerate all outcomes\n if (count <= 6 && support.length <= 20) {\n function dfs(\n rollsLeft: number,\n currentMax: number,\n probability: number\n ): void {\n if (rollsLeft === 0) {\n out.set(currentMax, (out.get(currentMax) || 0) + probability);\n return;\n }\n\n for (const value of support) {\n const p = pmf.pAt(value);\n if (p > 0) {\n const newMax = Math.max(currentMax, value);\n dfs(rollsLeft - 1, newMax, probability * p);\n }\n }\n }\n\n dfs(count, -Infinity, 1);\n } else {\n // For larger cases, use the CDF method. Walk the sorted support once while\n // accumulating a running CDF, so each P(max = v) costs O(1) instead of a\n // full-map cdfAt() scan (previously O(N) per value → O(N²) overall).\n // Between two consecutive support points there is no probability mass, so\n // the running CDF up to (but not including) v equals cdfAt(v - 1).\n const sortedSupport = [...support].sort((a, b) => a - b);\n let runningCdf = 0;\n for (const value of sortedSupport) {\n const prevCdf = runningCdf;\n runningCdf += pmf.pAt(value);\n\n // P(max = value) = P(all rolls <= value) - P(all rolls <= value-1)\n const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count);\n if (probMax > eps) {\n out.set(value, probMax);\n }\n }\n }\n\n return PMF.fromMap(out, eps);\n}\n\nfunction keepSumPMF(\n single: PMF,\n total: number,\n keep: number,\n highest: boolean,\n eps: number = defaultEps\n): PMF {\n // Trivial/fast paths\n if (keep >= total) return single.power(total, eps);\n if (keep <= 0) return PMF.delta(0, eps);\n\n const sortedSupport = [...single.support()].sort((a, b) => a - b);\n const pmfSig = sortedSupport\n .map((val) => `${val}:${single.pAt(val).toPrecision(6)}`)\n .join(\",\");\n const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${\n highest ? 1 : 0\n }|e:${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n // kh1/kl1 fast paths using max-of machinery\n if (keep === 1) {\n if (highest) {\n return computeMaxOfPMF(single, total, eps);\n } else {\n // min of n i.i.d. == -max of n of negated variable\n const neg = single.mapDamage((v) => -v);\n const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v);\n builderPMFCache.set(cacheKey, minPMF);\n return minPMF;\n }\n }\n\n // DP over descending values; state = (used, remainingTrials) → map(sum -> prob)\n // Transition by drawing X occurrences at current value v from remainingTrials r: X ~ Binom(r, p)\n // Select t = min(X, keep - used) into the sum (highest picks first), then continue with r - X.\n\n type SumMap = Map;\n let state: Map = new Map();\n // Pack the (used, remainingTrials) state into a single integer key instead of\n // a \"used|r\" string. r ∈ [0, total], so a stride of (total + 1) is collision\n // free, and decoding is plain integer math — no split()/parseInt() per\n // transition in the hot loop. Behavior is identical (same states, same order).\n const stride = total + 1;\n const keyOf = (used: number, r: number) => used * stride + r;\n\n state.set(keyOf(0, total), new Map([[0, 1]]));\n\n const valuesDesc = highest\n ? [...sortedSupport].sort((a, b) => b - a)\n : [...sortedSupport].sort((a, b) => a - b);\n\n const binomPMF = (r: number, p: number): number[] => {\n if (r <= 0) return [1];\n if (p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[0] = 1;\n return arr;\n }\n if (1 - p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[r] = 1;\n return arr;\n }\n const q = 1 - p;\n const arr = new Array(r + 1).fill(0);\n\n // stable recurrence from k=0\n arr[0] = Math.pow(q, r);\n const ratio = p / q;\n for (let x = 1; x <= r; x++)\n arr[x] = ((arr[x - 1] * (r - x + 1)) / x) * ratio;\n\n // Normalize minor drift\n let s = 0;\n for (let x = 0; x <= r; x++) s += arr[x];\n if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s;\n\n return arr;\n };\n\n const pruneMap = (m: SumMap, threshold: number): SumMap => {\n if (threshold <= 0) return m;\n const out = new Map();\n for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr);\n return out.size === m.size ? m : out;\n };\n\n const pruneState = (st: Map, threshold: number) => {\n if (threshold <= 0) return st;\n const out = new Map();\n for (const [k, m] of st) {\n const mm = pruneMap(m, threshold);\n if (mm.size > 0) out.set(k, mm);\n }\n return out;\n };\n\n let processedMass = 0;\n for (const v of valuesDesc) {\n const p = single.pAt(v);\n if (p <= 0) continue;\n const q = Math.max(eps, 1 - processedMass);\n const pCond = Math.min(1, p / q);\n const next: Map = new Map();\n\n for (const [k, m] of state) {\n const used = Math.floor(k / stride);\n const r = k - used * stride;\n if (r === 0) {\n // No trials left; carry state forward unchanged\n const destKey = keyOf(used, 0);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr);\n next.set(destKey, dest);\n continue;\n }\n\n const bin = binomPMF(r, pCond);\n const remainingCapacity = keep - used;\n\n for (let x = 0; x <= r; x++) {\n const px = bin[x];\n if (px <= eps) continue;\n const t = Math.min(x, remainingCapacity);\n const used2 = used + t;\n const r2 = r - x;\n const add = t * v;\n\n const destKey = keyOf(used2, r2);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) {\n const s2 = sum + add;\n const prob = pr * px;\n const cur = dest.get(s2) || 0;\n const nv = cur + prob;\n if (nv >= eps) dest.set(s2, nv);\n }\n if (dest.size > 0) next.set(destKey, dest);\n }\n }\n\n // Light pruning proportional to eps\n state = pruneState(next, eps * 1e-6);\n processedMass += p;\n }\n\n // Collect results where all trials assigned and exactly keep were used\n const finalKey = keyOf(keep, 0);\n const dist = state.get(finalKey) ?? new Map();\n\n if (dist.size === 0) {\n // Fallback safety: return empty mass (should not happen)\n return PMF.emptyMass();\n }\n\n const result = PMF.fromMap(dist, eps);\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function getASTSignature(node: ExpressionNode): string {\n switch (node.type) {\n case \"constant\":\n return `c:${node.value}`;\n case \"die\": {\n // Use a fixed order for properties to ensure a stable signature.\n const parts: string[] = [];\n parts.push(`s:${node.sides}`);\n if (node.reroll) parts.push(`r:${node.reroll}`);\n if (node.minimum) parts.push(`m:${node.minimum}`);\n if (node.explode) parts.push(`e:${node.explode}`);\n return `d{${parts.join(\",\")}}`;\n }\n case \"sum\":\n return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"d20Roll\":\n return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`;\n case \"keep\":\n return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(\n node.child\n )}}`;\n case \"half\":\n return `half{ch:${getASTSignature(node.child)}}`;\n case \"maxOf\":\n return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"scale\":\n return `scale{n:${node.numerator},d:${node.denominator},r:${\n node.rounding\n },ch:${getASTSignature(node.child)}}`;\n case \"add\": {\n let constantValue = 0;\n const otherChildrenSigs: string[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n constantValue += c.sign * c.node.value;\n } else {\n otherChildrenSigs.push(\n `${c.sign === -1 ? \"-\" : \"+\"}${getASTSignature(c.node)}`\n );\n }\n }\n\n if (constantValue !== 0) {\n otherChildrenSigs.push(\n constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}`\n );\n }\n\n // Sort to handle commutative nature of addition.\n otherChildrenSigs.sort();\n\n return `add[${otherChildrenSigs.join(\"\")}]`;\n }\n }\n}\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport type { ACBuilder } from \"./ac\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport {\n AlwaysCritBuilder,\n AlwaysHitBuilder,\n ParsedRollBuilder,\n RollBuilder,\n} from \"./roll\";\nimport type { AttackResolution, CheckBuilder } from \"./types\";\n\ntype ActionEffect = RollBuilder;\n\nexport class AttackBuilder implements CheckBuilder {\n constructor(\n readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n private readonly hitEffect?: ActionEffect,\n private readonly critEffect?: ActionEffect | null,\n private readonly missEffect?: ActionEffect\n ) {}\n\n onCrit(val: number): AttackBuilder;\n onCrit(val: string): AttackBuilder;\n onCrit(val: RollBuilder): AttackBuilder;\n onCrit(count: number, die: RollBuilder): AttackBuilder;\n onCrit(count: number, sides: number): AttackBuilder;\n onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onCrit(count: number, sides: number, modifier: number): AttackBuilder;\n onCrit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n damageRoll,\n this.missEffect\n );\n }\n\n onMiss(val: number): AttackBuilder;\n onMiss(val: string): AttackBuilder;\n onMiss(val: RollBuilder): AttackBuilder;\n onMiss(count: number, die: RollBuilder): AttackBuilder;\n onMiss(count: number, sides: number): AttackBuilder;\n onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onMiss(count: number, sides: number, modifier: number): AttackBuilder;\n onMiss(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n this.critEffect,\n damageRoll\n );\n }\n\n noCrit(): AttackBuilder {\n return new AttackBuilder(this.check, this.hitEffect, null, this.missEffect);\n }\n\n // Legacy expressions\n toExpression(): string {\n const checkPart = this.check.toExpression();\n\n let effectPart = \"\";\n\n if (this.hitEffect) {\n effectPart = `(${this.hitEffect.toExpression()})`;\n if (this.critEffect !== null) {\n let crit: RollBuilder;\n if (this.critEffect) {\n crit = this.critEffect;\n } else {\n // For ParsedRollBuilder, we can't double dice, so skip the crit expression\n if (this.hitEffect instanceof ParsedRollBuilder) {\n // Don't try to double ParsedRollBuilder - leave it out of expression\n crit = RollBuilder.fromArgs(0);\n } else {\n crit =\n this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);\n }\n }\n\n const critThreshold = this.check.critThreshold;\n if (critThreshold < 1 || critThreshold > 20) {\n throw new Error(\n `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.`\n );\n }\n\n // Only include crit expression if crit is not zero\n const critExpression = crit.toExpression();\n if (critExpression !== \"0\") {\n if (critThreshold === 20) {\n effectPart += ` crit (${critExpression})`;\n } else {\n const xcritNumber = 21 - critThreshold;\n effectPart += ` xcrit${xcritNumber} (${critExpression})`;\n }\n }\n }\n\n if (this.missEffect) {\n effectPart += ` miss (${this.missEffect.toExpression()})`;\n }\n }\n\n return `${checkPart} * ${effectPart}`;\n }\n\n resolveProbabilities(\n check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n eps: number = 0\n ): { pSuccess: number; pHit: number; pCrit: number; pMiss: number } {\n const rollType = check.rollType;\n const rerollOne = check.baseReroll > 0;\n\n const critThreshold = check.critThreshold;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n if (check instanceof AlwaysCritBuilder) {\n // If fromAlwaysHit is true, everything is a crit (no misses)\n if (check.fromAlwaysHit) {\n return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };\n }\n\n // If fromAlwaysHit is false (came from ACBuilder), we need to check AC\n // Natural 1s always miss, everything else that would hit becomes a crit\n const ac = check.attackConfig.ac ?? 0;\n const staticMod = this.check.modifier;\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Natural 1 always misses\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Check if this roll would hit the AC\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n // Everything that hits becomes a crit\n pcrit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n return { pSuccess: pcrit, pHit: 0, pCrit: pcrit, pMiss: pmiss };\n }\n\n if (check instanceof AlwaysHitBuilder) {\n // Preserve rollType for crit odds\n let pCrit = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n if (r >= critThreshold) pCrit += pr;\n }\n const pHit = 1 - pCrit;\n const pMiss = 0;\n\n return { pSuccess: 1, pHit, pCrit, pMiss };\n }\n\n const ac = check.attackConfig.ac;\n const staticMod = this.check.modifier;\n\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let phit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Handle auto-miss\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Handle crit\n if (r >= critThreshold) {\n pcrit += pr;\n continue;\n }\n\n // Handle normal hit/miss\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n phit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n const psuccess = phit + pcrit;\n return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss };\n }\n\n resolve(eps: number = EPS): AttackResolution {\n const {\n pHit,\n pCrit,\n pMiss: pmiss,\n } = this.resolveProbabilities(this.check, eps);\n const hitPMF = this.hitEffect\n ? this.hitEffect instanceof ParsedRollBuilder\n ? this.hitEffect.toPMF(eps)\n : pmfFromRollBuilder(this.hitEffect, eps)\n : PMF.delta(0, eps);\n\n let critPMF: PMF | null = null;\n let phit = pHit;\n let pcrit = pCrit;\n\n if (this.critEffect === null) {\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n } else {\n let critBuilder: RollBuilder | undefined;\n \n if (this.critEffect) {\n critBuilder = this.critEffect;\n } else if (this.hitEffect instanceof ParsedRollBuilder) {\n // For ParsedRollBuilder, we can't automatically double dice\n // So treat it as noCrit() - roll crit probability into hit\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n critBuilder = undefined;\n } else {\n critBuilder = this.hitEffect?.copy().doubleDice();\n }\n\n if (critBuilder) {\n critPMF = critBuilder instanceof ParsedRollBuilder\n ? critBuilder.toPMF(eps)\n : pmfFromRollBuilder(critBuilder, eps);\n }\n }\n const missPMF = this.missEffect\n ? this.missEffect instanceof ParsedRollBuilder\n ? this.missEffect.toPMF(eps)\n : pmfFromRollBuilder(this.missEffect, eps)\n : PMF.delta(0, eps);\n\n // Mix them up\n const mix = new Mixture(eps);\n if (phit > 0) mix.add(\"hit\", hitPMF, phit);\n if (critPMF && pcrit > 0) mix.add(\"crit\", critPMF, pcrit);\n if (pmiss > 0)\n mix.add(this.missEffect ? \"missDamage\" : \"missNone\", missPMF, pmiss);\n\n return {\n pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps),\n check: this.check.toPMF(eps) ?? PMF.delta(0, eps),\n hit: hitPMF ?? PMF.delta(0, eps),\n crit: critPMF ?? PMF.delta(0, eps),\n miss: missPMF ?? PMF.delta(0, eps),\n weights: { hit: phit, crit: pcrit, miss: pmiss },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport { AlwaysCritBuilder, RollBuilder } from \"./roll\";\n\nexport interface AttackConfig {\n ac: number;\n critThreshold: number;\n}\nexport class ACBuilder extends RollBuilder {\n readonly attackConfig: AttackConfig;\n\n constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig) {\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig, ac };\n } else {\n this.attackConfig = { ac, critThreshold: 20 };\n }\n }\n\n // onHit(effect: RollBuilder): AttackBuilder {\n // return new AttackBuilder(this).onHit(effect)\n // }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(threshold: number): ACBuilder {\n const newConfig: AttackConfig = {\n ...this.attackConfig,\n critThreshold: threshold,\n };\n return new ACBuilder(this, this.attackConfig.ac, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(\n this,\n {\n critThreshold: this.attackConfig.critThreshold,\n ac: this.attackConfig.ac,\n },\n false\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs(); // This already includes bonus dice, no need to add them again\n const expression = new RollBuilder(configs).toExpression();\n return this.attackConfig.ac\n ? `(${expression} AC ${this.attackConfig.ac})`\n : expression;\n }\n\n override toPMF(eps: number = 0): PMF {\n const ac = this.attackConfig.ac;\n\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n const staticMod = this.modifier;\n const bonusPMFs = this.getBonusDicePMFs(this, eps);\n\n // Build total to-hit value distribution attackRollPMF = d20 ⊕ bonusDice, then shift by staticMod\n const parts = [d20, ...bonusPMFs];\n let attackRollPMF = parts.length === 1 ? d20 : PMF.convolveMany(parts, eps);\n if (staticMod !== 0)\n attackRollPMF = attackRollPMF.mapDamage(\n (rollValue) => rollValue + staticMod\n );\n\n // Map to 0 when below AC\n const out = new Map();\n for (const rollValue of attackRollPMF.support()) {\n const p = attackRollPMF.pAt(rollValue);\n const key = rollValue >= ac ? rollValue : 0;\n out.set(key, (out.get(key) || 0) + p);\n }\n return PMF.fromMap(out, eps);\n }\n\n override copy(): ACBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const newConfig = {\n ac: this.attackConfig.ac,\n critThreshold: this.attackConfig.critThreshold,\n };\n return new ACBuilder(baseCopy, newConfig.ac, newConfig);\n }\n}\n\n// Augment the RollBuilder prototype to implement the ac method\nRollBuilder.prototype.ac = function (targetAC: number): ACBuilder {\n if (isNaN(targetAC)) throw new Error(\"Invalid NaN value for targetAC\");\n return new ACBuilder(this, targetAC);\n};\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport type { DCBuilder } from \"./dc\";\nimport { ParsedRollBuilder, type RollBuilder } from \"./roll\";\nimport type { CheckBuilder, SaveResolution } from \"./types\";\n\nexport type SaveOutcome = \"normal\" | \"half\";\n\nexport class SaveBuilder implements CheckBuilder {\n constructor(\n readonly check: DCBuilder,\n private readonly failureEffect?: RollBuilder,\n private readonly saveOutcome: SaveOutcome = \"normal\"\n ) {}\n\n saveHalf(): SaveBuilder {\n return new SaveBuilder(this.check, this.failureEffect, \"half\");\n }\n\n toExpression(): string {\n const checkPart = this.check.toExpression();\n if (!this.failureEffect) return checkPart;\n\n const failureEffectPart = this.failureEffect.toExpression();\n const result = `${checkPart} * (${failureEffectPart})`;\n return this.saveOutcome === \"half\" ? `${result} save half` : result;\n }\n\n resolve(eps: number = EPS): SaveResolution {\n const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities(\n this.check\n );\n const failPMF = this.failureEffect\n ? this.failureEffect instanceof ParsedRollBuilder\n ? this.failureEffect.toPMF(eps)\n : pmfFromRollBuilder(this.failureEffect)\n : PMF.delta(0);\n const onSuccess = this.saveOutcome ?? \"half\";\n\n let successPMF: PMF = PMF.delta(0, eps);\n if (onSuccess === \"half\") successPMF = failPMF.scaleDamage(0.5, \"floor\");\n\n const successLabel: OutcomeType =\n onSuccess === \"normal\" ? \"missNone\" : \"saveHalf\";\n const failLabel: OutcomeType = \"saveFail\";\n const baseMix = new Mixture(eps);\n const mixture = baseMix\n .add(successLabel, successPMF, psuccess)\n .add(failLabel, failPMF, pfail);\n\n return {\n pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps),\n check:\n PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps),\n saveFail: failPMF ?? PMF.delta(0, eps),\n saveSuccess: successPMF ?? PMF.delta(0, eps),\n weights: { success: psuccess, fail: pfail },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n\nfunction resolveProbabilities(check: DCBuilder): {\n pSuccess: number;\n pFail: number;\n} {\n const saveBonus = check.modifier;\n const dc = check.saveDC;\n const d20Type = check.rollType;\n const baseReroll = check.baseReroll;\n // TODO later check if base reroll is not 0 or 1.\n\n const die = d20RollPMF(d20Type, baseReroll > 0);\n const faceP = new Map();\n for (const [r, bin] of die) {\n const pr = bin.p;\n if (pr > 0) faceP.set(r, pr);\n }\n\n // Now add bonus dice to the PMF (bless, bane, bardic, etc)\n const eps = 0;\n const bonusDicePMFs = check.getBonusDicePMFs(check, eps);\n const bonusPMF =\n bonusDicePMFs.length > 0\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.zero(eps);\n\n let pSuccess = 0;\n for (let r = 1; r <= 20; r++) {\n const pr = faceP.get(r);\n if (!pr) continue;\n const need = dc - saveBonus - r;\n pSuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pFail = Math.max(0, 1 - pSuccess);\n return { pSuccess, pFail: pFail };\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport { RollBuilder } from \"./roll\";\nimport { SaveBuilder } from \"./save\";\n\ninterface SaveConfig {\n dc: number;\n}\n\nexport class DCBuilder extends RollBuilder {\n private readonly saveConfig: SaveConfig;\n\n constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig) {\n super(baseRoll.getSubRollConfigs());\n this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 };\n }\n\n override dc(saveDC: number): DCBuilder {\n if (this.rollType && this.rollType === \"elven accuracy\") {\n throw new Error(\n \"Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead.\"\n );\n }\n return new DCBuilder(this, { dc: saveDC });\n }\n\n get saveDC(): number {\n return this.saveConfig.dc;\n }\n\n override add(anotherRoll: RollBuilder): DCBuilder {\n const newBuilder = super.add(anotherRoll);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n override addRoll(count?: number): DCBuilder {\n const newBuilder = super.addRoll(count);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n onSaveFailure(val: number): SaveBuilder;\n onSaveFailure(val: string): SaveBuilder;\n onSaveFailure(val: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, sides: number): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder;\n onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder;\n onSaveFailure(...args: any[]): SaveBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new SaveBuilder(this, damageRoll);\n }\n\n override withElvenAccuracy(): never {\n throw new Error(\n \"Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks).\"\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const subConfigs = this.getSubRollConfigs();\n const allConfigs = [...subConfigs];\n const expression = new RollBuilder(allConfigs).toExpression();\n return `(${expression} DC ${this.saveConfig.dc})`;\n }\n\n override toPMF(eps: number = 0): PMF {\n const saveDC = this.saveDC;\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n const staticMod = this.modifier;\n const bonusDicePMFs = this.getBonusDiceConfigs().map((cfg) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps)\n );\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let psuccess = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n const need = saveDC - staticMod - r;\n psuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pfail = Math.max(0, 1 - psuccess);\n const m = new Map([\n [0, psuccess > 0 ? psuccess : 0],\n [1, pfail > 0 ? pfail : 0],\n ]);\n return PMF.fromMap(m, eps);\n }\n}\n\n// Augment the RollBuilder prototype to implement the dc method\nRollBuilder.prototype.dc = function (saveDC: number): DCBuilder {\n if (isNaN(saveDC)) throw new Error(\"Invalid NaN value for saveDC\");\n return new DCBuilder(this).dc(saveDC);\n};\n"]} \ No newline at end of file diff --git a/dist/builder/index.d.cts b/dist/builder/index.d.cts new file mode 100644 index 0000000..3c21b41 --- /dev/null +++ b/dist/builder/index.d.cts @@ -0,0 +1,429 @@ +import { b as RollType, P as PMF, l as DiceQuery, C as CritConfig, L as LRUCache } from '../pmf-D5VRghZI.cjs'; + +type RollFactory = { + (count: number, sides?: number, modifier?: number): RollBuilder; + (count: number, die: RollBuilder, modifier?: number): RollBuilder; + d(sides: number | string): RollBuilder; + hd20(): RollBuilder; + d4(): RollBuilder; + d6(): RollBuilder; + d8(): RollBuilder; + d10(): RollBuilder; + d12(): RollBuilder; + d20(): RollBuilder; + d100(): RollBuilder; + flat(n: number): RollBuilder; +}; +type KeepMode = "highest" | "lowest"; +type RollConfig = { + count: number; + sides: number; + modifier: number; + reroll: number; + explode: number; + minimum: number; + bestOf: number; + keep: { + total: number; + count: number; + mode: KeepMode; + } | undefined; + rollType: RollType; + isSubtraction?: boolean; +}; +type Resolution = { + pmf: PMF; + check: PMF; + weights: { + [key: string]: number; + }; +}; +type AttackResolution = Resolution & { + hit: PMF; + crit: PMF; + miss: PMF; + weights: { + hit: number; + crit: number; + miss: number; + }; +}; +type SaveResolution = Resolution & { + saveFail: PMF; + saveSuccess: PMF; + weights: { + success: number; + fail: number; + }; +}; +interface CheckBuilder { + resolve(eps?: number): Resolution; + toExpression(): string; + readonly pmf: PMF; +} + +type SaveOutcome = "normal" | "half"; +declare class SaveBuilder implements CheckBuilder { + readonly check: DCBuilder; + private readonly failureEffect?; + private readonly saveOutcome; + constructor(check: DCBuilder, failureEffect?: RollBuilder | undefined, saveOutcome?: SaveOutcome); + saveHalf(): SaveBuilder; + toExpression(): string; + resolve(eps?: number): SaveResolution; + toPMF(eps?: number): PMF; + get pmf(): PMF; + toQuery(eps?: number): DiceQuery; +} + +interface SaveConfig { + dc: number; +} +declare class DCBuilder extends RollBuilder { + private readonly saveConfig; + constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig); + dc(saveDC: number): DCBuilder; + get saveDC(): number; + add(anotherRoll: RollBuilder): DCBuilder; + addRoll(count?: number): DCBuilder; + onSaveFailure(val: number): SaveBuilder; + onSaveFailure(val: string): SaveBuilder; + onSaveFailure(val: RollBuilder): SaveBuilder; + onSaveFailure(count: number, die: RollBuilder): SaveBuilder; + onSaveFailure(count: number, sides: number): SaveBuilder; + onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder; + onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder; + withElvenAccuracy(): never; + toExpression(): string; + toPMF(eps?: number): PMF; +} + +type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode | ScaleNode; +type DieNode = { + type: "die"; + sides: number; + reroll?: number; + minimum?: number; + explode?: number; +}; +type ConstantNode = { + type: "constant"; + value: number; +}; +type SumNode = { + type: "sum"; + count: number; + child: ExpressionNode; +}; +type AddNode = { + type: "add"; + children: { + node: ExpressionNode; + sign: 1 | -1; + }[]; +}; +type KeepNode = { + type: "keep"; + mode: "highest" | "lowest"; + count: number; + child: SumNode | KeepNode; +}; +type D20RollNode = { + type: "d20Roll"; + rollType: "advantage" | "disadvantage" | "elven accuracy"; + child: ExpressionNode; +}; +type HalfNode = { + type: "half"; + child: ExpressionNode; +}; +type MaxOfNode = { + type: "maxOf"; + count: number; + child: ExpressionNode; +}; +/** + * Scale the child's result by `numerator / denominator`, then round. + * + * Unlike {@link HalfNode} (a fixed `// 2` with floor), this is a general, composable + * multiplier/divider — the building block for damage-type resistance (`1/2`, floor), + * vulnerability (`2/1`), and similar per-source transforms. It renders as + * `N * (child)` when the denominator is 1, `(child) // D` when the numerator is 1, + * and `(child) * N // D` otherwise. + */ +type ScaleNode = { + type: "scale"; + numerator: number; + denominator: number; + rounding: "floor" | "round" | "ceil"; + child: ExpressionNode; +}; + +declare const defaultConfig: RollConfig; +declare class RollBuilder { + protected readonly subRollConfigs: readonly RollConfig[]; + constructor(countOrConfigs?: number | readonly RollConfig[]); + protected create(configs: readonly RollConfig[]): RollBuilder; + protected get lastConfig(): RollConfig; + hasHiddenState(): boolean; + getSubRollConfigs(): readonly RollConfig[]; + static fromConfig(config: Partial): RollBuilder; + static fromConfigs(configs: Partial[]): RollBuilder; + static fromArgs(...args: any[]): RollBuilder; + d(sides: number | undefined): RollBuilder; + plus(modOrRoll: number | RollBuilder | undefined): RollBuilder; + plus(count: number, die: RollBuilder): RollBuilder; + minus(modOrRoll: number | RollBuilder | undefined): RollBuilder; + minus(count: number, die: RollBuilder): RollBuilder; + /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ + reroll(value: number): RollBuilder; + /** Set finite explode count for max-face explosions (Infinity allowed). */ + explode(count?: number | undefined): RollBuilder; + /** Apply per-die minimum value (min > 0). */ + minimum(val: number | undefined): RollBuilder; + bestOf(count: number | undefined): RollBuilder; + keepHighest(total: number, count: number): RollBuilder; + keepLowest(total: number, count: number): RollBuilder; + keepHighestAll(total: number, count: number): PooledRollBuilder; + keepLowestAll(total: number, count: number): PooledRollBuilder; + withAdvantage(): RollBuilder; + withDisadvantage(): RollBuilder; + add(anotherRoll: RollBuilder | undefined): RollBuilder; + withBonus(anotherRoll: RollBuilder): RollBuilder; + addRoll(count?: number): RollBuilder; + scaleDice(scale: number): RollBuilder; + doubleDice(): RollBuilder; + alwaysHits(): AlwaysHitBuilder; + alwaysCrits(): AlwaysCritBuilder; + copy(): RollBuilder; + d4: () => RollBuilder; + d6: () => RollBuilder; + d8: () => RollBuilder; + d10: () => RollBuilder; + d12: () => RollBuilder; + d20: () => RollBuilder; + d100: () => RollBuilder; + withElvenAccuracy(): RollBuilder; + toExpression(): string; + toPMF(eps?: number): PMF; + get pmf(): PMF; + toQuery(eps?: number): DiceQuery; + toAST(): ExpressionNode; + private configToSingleExpressionWithoutModifier; + getRootDieConfig(): RollConfig | undefined; + getAllDieConfigs(): readonly RollConfig[]; + getBonusDiceConfigs(): RollConfig[]; + getBonusDicePMFs(check: RollBuilder, eps?: number): PMF[]; + get modifier(): number; + get rollType(): RollType; + get baseReroll(): number; + half(): HalfRollBuilder; + /** + * Scale this roll's result by `numerator / denominator`, rounding each outcome. + * A general, composable form of {@link half} — used to model damage-type resistance + * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). + * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. + */ + scaleResult(numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"): ScaleRollBuilder; + maxOf(count: number): MaxOfRollBuilder; + ac(_targetAC: number): ACBuilder; + dc(_saveDC: number): DCBuilder; +} +declare class HalfRollBuilder extends RollBuilder { + private readonly innerRoll; + constructor(innerRoll: RollBuilder); + hasHiddenState(): boolean; + get lastConfig(): RollConfig; + getSubRollConfigs(): readonly RollConfig[]; + toExpression(): string; + toAST(): ExpressionNode; + toPMF(eps?: number): PMF; + copy(): HalfRollBuilder; +} +/** + * A roll whose result is scaled by `numerator / denominator` and rounded — the composable + * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or + * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls. + */ +declare class ScaleRollBuilder extends RollBuilder { + private readonly innerRoll; + private readonly numerator; + private readonly denominator; + private readonly rounding; + constructor(innerRoll: RollBuilder, numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"); + hasHiddenState(): boolean; + get lastConfig(): RollConfig; + getSubRollConfigs(): readonly RollConfig[]; + toExpression(): string; + toAST(): ExpressionNode; + toPMF(eps?: number): PMF; + copy(): ScaleRollBuilder; +} +declare class MaxOfRollBuilder extends RollBuilder { + private readonly innerRoll; + private readonly count; + private readonly diceCount?; + private readonly diceSides?; + constructor(innerRoll: RollBuilder, count: number, diceCount?: number | undefined, diceSides?: number | undefined); + hasHiddenState(): boolean; + get lastConfig(): RollConfig; + getSubRollConfigs(): readonly RollConfig[]; + toExpression(): string; + toAST(): ExpressionNode; + toPMF(eps?: number): PMF; + copy(): MaxOfRollBuilder; +} +declare class AlwaysHitBuilder extends RollBuilder { + readonly attackConfig: CritConfig; + constructor(baseRoll: RollBuilder, attackConfig?: CritConfig); + protected create(configs: readonly RollConfig[]): RollBuilder; + onHit(val: number): AttackBuilder; + onHit(val: string): AttackBuilder; + onHit(val: RollBuilder): AttackBuilder; + onHit(count: number, die: RollBuilder): AttackBuilder; + onHit(count: number, sides: number): AttackBuilder; + onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onHit(count: number, sides: number, modifier: number): AttackBuilder; + get critThreshold(): number; + critOn(critThreshold: number): AlwaysHitBuilder; + alwaysCrits(): AlwaysCritBuilder; + toExpression(): string; + toPMF(): PMF; + copy(): AlwaysHitBuilder; +} +declare class AlwaysCritBuilder extends RollBuilder { + readonly attackConfig: CritConfig & { + ac?: number; + }; + readonly fromAlwaysHit: boolean; + constructor(baseRoll: RollBuilder, attackConfig?: CritConfig & { + ac?: number; + }, fromAlwaysHit?: boolean); + protected create(configs: readonly RollConfig[]): RollBuilder; + onHit(val: number): AttackBuilder; + onHit(val: string): AttackBuilder; + onHit(val: RollBuilder): AttackBuilder; + onHit(count: number, die: RollBuilder): AttackBuilder; + onHit(count: number, sides: number): AttackBuilder; + onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onHit(count: number, sides: number, modifier: number): AttackBuilder; + get critThreshold(): number; + critOn(critThreshold: number): AlwaysCritBuilder; + toExpression(): string; + toPMF(): PMF; + copy(): AlwaysCritBuilder; +} +declare class ParsedRollBuilder extends RollBuilder { + private readonly cachedPMF; + private readonly originalExpression; + constructor(expression: string); + hasHiddenState(): boolean; + protected create(configs: readonly RollConfig[]): RollBuilder; + toPMF(_eps?: number): PMF; + toExpression(): string; + toAST(): ExpressionNode; + copy(): ParsedRollBuilder; + doubleDice(): ParsedRollBuilder; +} +declare class PooledRollBuilder extends RollBuilder { + private readonly baseAST; + private readonly baseExpression; + constructor(baseAST: ExpressionNode, baseExpression: string, configs?: readonly RollConfig[]); + protected create(configs: readonly RollConfig[]): PooledRollBuilder; + hasHiddenState(): boolean; + d(_sides: number | undefined): RollBuilder; + reroll(_value: number): RollBuilder; + explode(_count?: number | undefined): RollBuilder; + minimum(_val: number | undefined): RollBuilder; + bestOf(_count: number | undefined): RollBuilder; + keepHighest(_total: number, _count: number): RollBuilder; + keepLowest(_total: number, _count: number): RollBuilder; + withAdvantage(): RollBuilder; + withDisadvantage(): RollBuilder; + withElvenAccuracy(): RollBuilder; + toAST(): ExpressionNode; + toExpression(): string; + copy(): PooledRollBuilder; + scaleDice(scale: number): RollBuilder; + times(count: number): PooledRollBuilder; +} +/** + * Combine several rolls into one additive payload whose PMF is their convolution and whose + * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry + * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance + * and vulnerability survive into both the distribution and the rendered expression. + * Empty parts collapse to `0`; a single part is returned unwrapped. + */ +declare function sumRolls(parts: readonly RollBuilder[]): RollBuilder; + +type ActionEffect = RollBuilder; +declare class AttackBuilder implements CheckBuilder { + readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder; + private readonly hitEffect?; + private readonly critEffect?; + private readonly missEffect?; + constructor(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, hitEffect?: ActionEffect | undefined, critEffect?: (ActionEffect | null) | undefined, missEffect?: ActionEffect | undefined); + onCrit(val: number): AttackBuilder; + onCrit(val: string): AttackBuilder; + onCrit(val: RollBuilder): AttackBuilder; + onCrit(count: number, die: RollBuilder): AttackBuilder; + onCrit(count: number, sides: number): AttackBuilder; + onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onCrit(count: number, sides: number, modifier: number): AttackBuilder; + onMiss(val: number): AttackBuilder; + onMiss(val: string): AttackBuilder; + onMiss(val: RollBuilder): AttackBuilder; + onMiss(count: number, die: RollBuilder): AttackBuilder; + onMiss(count: number, sides: number): AttackBuilder; + onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onMiss(count: number, sides: number, modifier: number): AttackBuilder; + noCrit(): AttackBuilder; + toExpression(): string; + resolveProbabilities(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, eps?: number): { + pSuccess: number; + pHit: number; + pCrit: number; + pMiss: number; + }; + resolve(eps?: number): AttackResolution; + toPMF(eps?: number): PMF; + get pmf(): PMF; + toQuery(eps?: number): DiceQuery; +} + +interface AttackConfig { + ac: number; + critThreshold: number; +} +declare class ACBuilder extends RollBuilder { + readonly attackConfig: AttackConfig; + constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig); + onHit(val: number): AttackBuilder; + onHit(val: string): AttackBuilder; + onHit(val: RollBuilder): AttackBuilder; + onHit(count: number, die: RollBuilder): AttackBuilder; + onHit(count: number, sides: number): AttackBuilder; + onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onHit(count: number, sides: number, modifier: number): AttackBuilder; + get critThreshold(): number; + critOn(threshold: number): ACBuilder; + alwaysCrits(): AlwaysCritBuilder; + toExpression(): string; + toPMF(eps?: number): PMF; + copy(): ACBuilder; +} + +declare function d(sides: number | string): RollBuilder; +declare const d4: RollBuilder; +declare const d6: RollBuilder; +declare const d8: RollBuilder; +declare const d10: RollBuilder; +declare const d12: RollBuilder; +declare const d20: RollBuilder; +declare const hd20: RollBuilder; +declare const d100: RollBuilder; +declare const flat: (n: number) => RollBuilder; +declare const roll: RollFactory; +declare const builderPMFCache: LRUCache; + +export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, RollType, SaveBuilder, type SaveOutcome, type SaveResolution, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls }; diff --git a/dist/builder/index.d.ts b/dist/builder/index.d.ts new file mode 100644 index 0000000..6a898cf --- /dev/null +++ b/dist/builder/index.d.ts @@ -0,0 +1,429 @@ +import { b as RollType, P as PMF, l as DiceQuery, C as CritConfig, L as LRUCache } from '../pmf-D5VRghZI.js'; + +type RollFactory = { + (count: number, sides?: number, modifier?: number): RollBuilder; + (count: number, die: RollBuilder, modifier?: number): RollBuilder; + d(sides: number | string): RollBuilder; + hd20(): RollBuilder; + d4(): RollBuilder; + d6(): RollBuilder; + d8(): RollBuilder; + d10(): RollBuilder; + d12(): RollBuilder; + d20(): RollBuilder; + d100(): RollBuilder; + flat(n: number): RollBuilder; +}; +type KeepMode = "highest" | "lowest"; +type RollConfig = { + count: number; + sides: number; + modifier: number; + reroll: number; + explode: number; + minimum: number; + bestOf: number; + keep: { + total: number; + count: number; + mode: KeepMode; + } | undefined; + rollType: RollType; + isSubtraction?: boolean; +}; +type Resolution = { + pmf: PMF; + check: PMF; + weights: { + [key: string]: number; + }; +}; +type AttackResolution = Resolution & { + hit: PMF; + crit: PMF; + miss: PMF; + weights: { + hit: number; + crit: number; + miss: number; + }; +}; +type SaveResolution = Resolution & { + saveFail: PMF; + saveSuccess: PMF; + weights: { + success: number; + fail: number; + }; +}; +interface CheckBuilder { + resolve(eps?: number): Resolution; + toExpression(): string; + readonly pmf: PMF; +} + +type SaveOutcome = "normal" | "half"; +declare class SaveBuilder implements CheckBuilder { + readonly check: DCBuilder; + private readonly failureEffect?; + private readonly saveOutcome; + constructor(check: DCBuilder, failureEffect?: RollBuilder | undefined, saveOutcome?: SaveOutcome); + saveHalf(): SaveBuilder; + toExpression(): string; + resolve(eps?: number): SaveResolution; + toPMF(eps?: number): PMF; + get pmf(): PMF; + toQuery(eps?: number): DiceQuery; +} + +interface SaveConfig { + dc: number; +} +declare class DCBuilder extends RollBuilder { + private readonly saveConfig; + constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig); + dc(saveDC: number): DCBuilder; + get saveDC(): number; + add(anotherRoll: RollBuilder): DCBuilder; + addRoll(count?: number): DCBuilder; + onSaveFailure(val: number): SaveBuilder; + onSaveFailure(val: string): SaveBuilder; + onSaveFailure(val: RollBuilder): SaveBuilder; + onSaveFailure(count: number, die: RollBuilder): SaveBuilder; + onSaveFailure(count: number, sides: number): SaveBuilder; + onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder; + onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder; + withElvenAccuracy(): never; + toExpression(): string; + toPMF(eps?: number): PMF; +} + +type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode | ScaleNode; +type DieNode = { + type: "die"; + sides: number; + reroll?: number; + minimum?: number; + explode?: number; +}; +type ConstantNode = { + type: "constant"; + value: number; +}; +type SumNode = { + type: "sum"; + count: number; + child: ExpressionNode; +}; +type AddNode = { + type: "add"; + children: { + node: ExpressionNode; + sign: 1 | -1; + }[]; +}; +type KeepNode = { + type: "keep"; + mode: "highest" | "lowest"; + count: number; + child: SumNode | KeepNode; +}; +type D20RollNode = { + type: "d20Roll"; + rollType: "advantage" | "disadvantage" | "elven accuracy"; + child: ExpressionNode; +}; +type HalfNode = { + type: "half"; + child: ExpressionNode; +}; +type MaxOfNode = { + type: "maxOf"; + count: number; + child: ExpressionNode; +}; +/** + * Scale the child's result by `numerator / denominator`, then round. + * + * Unlike {@link HalfNode} (a fixed `// 2` with floor), this is a general, composable + * multiplier/divider — the building block for damage-type resistance (`1/2`, floor), + * vulnerability (`2/1`), and similar per-source transforms. It renders as + * `N * (child)` when the denominator is 1, `(child) // D` when the numerator is 1, + * and `(child) * N // D` otherwise. + */ +type ScaleNode = { + type: "scale"; + numerator: number; + denominator: number; + rounding: "floor" | "round" | "ceil"; + child: ExpressionNode; +}; + +declare const defaultConfig: RollConfig; +declare class RollBuilder { + protected readonly subRollConfigs: readonly RollConfig[]; + constructor(countOrConfigs?: number | readonly RollConfig[]); + protected create(configs: readonly RollConfig[]): RollBuilder; + protected get lastConfig(): RollConfig; + hasHiddenState(): boolean; + getSubRollConfigs(): readonly RollConfig[]; + static fromConfig(config: Partial): RollBuilder; + static fromConfigs(configs: Partial[]): RollBuilder; + static fromArgs(...args: any[]): RollBuilder; + d(sides: number | undefined): RollBuilder; + plus(modOrRoll: number | RollBuilder | undefined): RollBuilder; + plus(count: number, die: RollBuilder): RollBuilder; + minus(modOrRoll: number | RollBuilder | undefined): RollBuilder; + minus(count: number, die: RollBuilder): RollBuilder; + /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ + reroll(value: number): RollBuilder; + /** Set finite explode count for max-face explosions (Infinity allowed). */ + explode(count?: number | undefined): RollBuilder; + /** Apply per-die minimum value (min > 0). */ + minimum(val: number | undefined): RollBuilder; + bestOf(count: number | undefined): RollBuilder; + keepHighest(total: number, count: number): RollBuilder; + keepLowest(total: number, count: number): RollBuilder; + keepHighestAll(total: number, count: number): PooledRollBuilder; + keepLowestAll(total: number, count: number): PooledRollBuilder; + withAdvantage(): RollBuilder; + withDisadvantage(): RollBuilder; + add(anotherRoll: RollBuilder | undefined): RollBuilder; + withBonus(anotherRoll: RollBuilder): RollBuilder; + addRoll(count?: number): RollBuilder; + scaleDice(scale: number): RollBuilder; + doubleDice(): RollBuilder; + alwaysHits(): AlwaysHitBuilder; + alwaysCrits(): AlwaysCritBuilder; + copy(): RollBuilder; + d4: () => RollBuilder; + d6: () => RollBuilder; + d8: () => RollBuilder; + d10: () => RollBuilder; + d12: () => RollBuilder; + d20: () => RollBuilder; + d100: () => RollBuilder; + withElvenAccuracy(): RollBuilder; + toExpression(): string; + toPMF(eps?: number): PMF; + get pmf(): PMF; + toQuery(eps?: number): DiceQuery; + toAST(): ExpressionNode; + private configToSingleExpressionWithoutModifier; + getRootDieConfig(): RollConfig | undefined; + getAllDieConfigs(): readonly RollConfig[]; + getBonusDiceConfigs(): RollConfig[]; + getBonusDicePMFs(check: RollBuilder, eps?: number): PMF[]; + get modifier(): number; + get rollType(): RollType; + get baseReroll(): number; + half(): HalfRollBuilder; + /** + * Scale this roll's result by `numerator / denominator`, rounding each outcome. + * A general, composable form of {@link half} — used to model damage-type resistance + * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). + * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. + */ + scaleResult(numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"): ScaleRollBuilder; + maxOf(count: number): MaxOfRollBuilder; + ac(_targetAC: number): ACBuilder; + dc(_saveDC: number): DCBuilder; +} +declare class HalfRollBuilder extends RollBuilder { + private readonly innerRoll; + constructor(innerRoll: RollBuilder); + hasHiddenState(): boolean; + get lastConfig(): RollConfig; + getSubRollConfigs(): readonly RollConfig[]; + toExpression(): string; + toAST(): ExpressionNode; + toPMF(eps?: number): PMF; + copy(): HalfRollBuilder; +} +/** + * A roll whose result is scaled by `numerator / denominator` and rounded — the composable + * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or + * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls. + */ +declare class ScaleRollBuilder extends RollBuilder { + private readonly innerRoll; + private readonly numerator; + private readonly denominator; + private readonly rounding; + constructor(innerRoll: RollBuilder, numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"); + hasHiddenState(): boolean; + get lastConfig(): RollConfig; + getSubRollConfigs(): readonly RollConfig[]; + toExpression(): string; + toAST(): ExpressionNode; + toPMF(eps?: number): PMF; + copy(): ScaleRollBuilder; +} +declare class MaxOfRollBuilder extends RollBuilder { + private readonly innerRoll; + private readonly count; + private readonly diceCount?; + private readonly diceSides?; + constructor(innerRoll: RollBuilder, count: number, diceCount?: number | undefined, diceSides?: number | undefined); + hasHiddenState(): boolean; + get lastConfig(): RollConfig; + getSubRollConfigs(): readonly RollConfig[]; + toExpression(): string; + toAST(): ExpressionNode; + toPMF(eps?: number): PMF; + copy(): MaxOfRollBuilder; +} +declare class AlwaysHitBuilder extends RollBuilder { + readonly attackConfig: CritConfig; + constructor(baseRoll: RollBuilder, attackConfig?: CritConfig); + protected create(configs: readonly RollConfig[]): RollBuilder; + onHit(val: number): AttackBuilder; + onHit(val: string): AttackBuilder; + onHit(val: RollBuilder): AttackBuilder; + onHit(count: number, die: RollBuilder): AttackBuilder; + onHit(count: number, sides: number): AttackBuilder; + onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onHit(count: number, sides: number, modifier: number): AttackBuilder; + get critThreshold(): number; + critOn(critThreshold: number): AlwaysHitBuilder; + alwaysCrits(): AlwaysCritBuilder; + toExpression(): string; + toPMF(): PMF; + copy(): AlwaysHitBuilder; +} +declare class AlwaysCritBuilder extends RollBuilder { + readonly attackConfig: CritConfig & { + ac?: number; + }; + readonly fromAlwaysHit: boolean; + constructor(baseRoll: RollBuilder, attackConfig?: CritConfig & { + ac?: number; + }, fromAlwaysHit?: boolean); + protected create(configs: readonly RollConfig[]): RollBuilder; + onHit(val: number): AttackBuilder; + onHit(val: string): AttackBuilder; + onHit(val: RollBuilder): AttackBuilder; + onHit(count: number, die: RollBuilder): AttackBuilder; + onHit(count: number, sides: number): AttackBuilder; + onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onHit(count: number, sides: number, modifier: number): AttackBuilder; + get critThreshold(): number; + critOn(critThreshold: number): AlwaysCritBuilder; + toExpression(): string; + toPMF(): PMF; + copy(): AlwaysCritBuilder; +} +declare class ParsedRollBuilder extends RollBuilder { + private readonly cachedPMF; + private readonly originalExpression; + constructor(expression: string); + hasHiddenState(): boolean; + protected create(configs: readonly RollConfig[]): RollBuilder; + toPMF(_eps?: number): PMF; + toExpression(): string; + toAST(): ExpressionNode; + copy(): ParsedRollBuilder; + doubleDice(): ParsedRollBuilder; +} +declare class PooledRollBuilder extends RollBuilder { + private readonly baseAST; + private readonly baseExpression; + constructor(baseAST: ExpressionNode, baseExpression: string, configs?: readonly RollConfig[]); + protected create(configs: readonly RollConfig[]): PooledRollBuilder; + hasHiddenState(): boolean; + d(_sides: number | undefined): RollBuilder; + reroll(_value: number): RollBuilder; + explode(_count?: number | undefined): RollBuilder; + minimum(_val: number | undefined): RollBuilder; + bestOf(_count: number | undefined): RollBuilder; + keepHighest(_total: number, _count: number): RollBuilder; + keepLowest(_total: number, _count: number): RollBuilder; + withAdvantage(): RollBuilder; + withDisadvantage(): RollBuilder; + withElvenAccuracy(): RollBuilder; + toAST(): ExpressionNode; + toExpression(): string; + copy(): PooledRollBuilder; + scaleDice(scale: number): RollBuilder; + times(count: number): PooledRollBuilder; +} +/** + * Combine several rolls into one additive payload whose PMF is their convolution and whose + * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry + * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance + * and vulnerability survive into both the distribution and the rendered expression. + * Empty parts collapse to `0`; a single part is returned unwrapped. + */ +declare function sumRolls(parts: readonly RollBuilder[]): RollBuilder; + +type ActionEffect = RollBuilder; +declare class AttackBuilder implements CheckBuilder { + readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder; + private readonly hitEffect?; + private readonly critEffect?; + private readonly missEffect?; + constructor(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, hitEffect?: ActionEffect | undefined, critEffect?: (ActionEffect | null) | undefined, missEffect?: ActionEffect | undefined); + onCrit(val: number): AttackBuilder; + onCrit(val: string): AttackBuilder; + onCrit(val: RollBuilder): AttackBuilder; + onCrit(count: number, die: RollBuilder): AttackBuilder; + onCrit(count: number, sides: number): AttackBuilder; + onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onCrit(count: number, sides: number, modifier: number): AttackBuilder; + onMiss(val: number): AttackBuilder; + onMiss(val: string): AttackBuilder; + onMiss(val: RollBuilder): AttackBuilder; + onMiss(count: number, die: RollBuilder): AttackBuilder; + onMiss(count: number, sides: number): AttackBuilder; + onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onMiss(count: number, sides: number, modifier: number): AttackBuilder; + noCrit(): AttackBuilder; + toExpression(): string; + resolveProbabilities(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, eps?: number): { + pSuccess: number; + pHit: number; + pCrit: number; + pMiss: number; + }; + resolve(eps?: number): AttackResolution; + toPMF(eps?: number): PMF; + get pmf(): PMF; + toQuery(eps?: number): DiceQuery; +} + +interface AttackConfig { + ac: number; + critThreshold: number; +} +declare class ACBuilder extends RollBuilder { + readonly attackConfig: AttackConfig; + constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig); + onHit(val: number): AttackBuilder; + onHit(val: string): AttackBuilder; + onHit(val: RollBuilder): AttackBuilder; + onHit(count: number, die: RollBuilder): AttackBuilder; + onHit(count: number, sides: number): AttackBuilder; + onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; + onHit(count: number, sides: number, modifier: number): AttackBuilder; + get critThreshold(): number; + critOn(threshold: number): ACBuilder; + alwaysCrits(): AlwaysCritBuilder; + toExpression(): string; + toPMF(eps?: number): PMF; + copy(): ACBuilder; +} + +declare function d(sides: number | string): RollBuilder; +declare const d4: RollBuilder; +declare const d6: RollBuilder; +declare const d8: RollBuilder; +declare const d10: RollBuilder; +declare const d12: RollBuilder; +declare const d20: RollBuilder; +declare const hd20: RollBuilder; +declare const d100: RollBuilder; +declare const flat: (n: number) => RollBuilder; +declare const roll: RollFactory; +declare const builderPMFCache: LRUCache; + +export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, RollType, SaveBuilder, type SaveOutcome, type SaveResolution, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls }; diff --git a/dist/builder/index.js b/dist/builder/index.js new file mode 100644 index 0000000..bf9fda0 --- /dev/null +++ b/dist/builder/index.js @@ -0,0 +1,5474 @@ +// src/common/lru-cache.ts +var LRUCache = class { + constructor(maxSize = 1e3) { + this.maxSize = maxSize; + this.cache = /* @__PURE__ */ new Map(); + } + get(key) { + const value = this.cache.get(key); + if (value === void 0) return void 0; + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + delete(key) { + this.cache.delete(key); + } + set(key, value) { + if (this.cache.size >= this.maxSize && !this.cache.has(key)) { + const oldestKey = this.cache.keys().next().value; + this.cache.delete(oldestKey); + } + this.cache.delete(key); + this.cache.set(key, value); + return this; + } + clear() { + this.cache.clear(); + } + get size() { + return this.cache.size; + } + has(key) { + return this.cache.has(key); + } + keys() { + return this.cache.keys(); + } + values() { + return this.cache.values(); + } +}; + +// src/common/types.ts +var EPS = 1e-12; +var MISS_NONE_OUTCOME = "missNone"; + +// src/pmf/query.ts +var _DiceQuery = class _DiceQuery { + constructor(singles, combined, eps = EPS) { + this.singles = Array.isArray(singles) ? singles : [singles]; + if (this.singles.some((s) => s === void 0)) { + throw new Error("DiceQuery contains undefined singles"); + } + this._eps = eps; + this._combinedProvided = combined !== void 0; + if (combined !== void 0) { + this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); + } + } + /** + * The combined damage distribution of all single PMFs (their convolution), + * normalized to total probability 1. + * + * Computed lazily on first access and cached. Queries that only need + * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, + * {@link DiceQuery.stddev} — never trigger this convolution. + */ + get combined() { + if (this._combined === void 0) { + const c = PMF.convolveMany(this.singles); + this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); + } + return this._combined; + } + /** + * Returns a new PMF with damage attribution metadata populated. + * + * This method computes attribution on-demand for builder-generated PMFs, + * enabling them to work with damage attribution charts. The `attr` field + * tracks how much damage each outcome type contributes at each damage value. + * + * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) + * + * Performance: Cached after first call. Adds minimal overhead vs `combined`. + * + * @returns PMF with attr field populated for damage attribution charts + * + * @example + * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) + * const query = attack.toQuery() + * const pmf = query.combinedWithAttribution() + * // Now pmf can be used with toDamageAttributionChartSeries() + */ + combinedWithAttribution() { + if (this._combinedWithAttr) { + return this._combinedWithAttr; + } + if (this.singles.every((pmf) => pmf.hasAttribution())) { + this._combinedWithAttr = this.combined; + return this._combinedWithAttr; + } + const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); + const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); + const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); + this._combinedWithAttr = normalized; + return normalized; + } + /** + * Per-label `damage value → probability mass` series for the combined, + * attribution-carrying distribution — the provenance core of the stacked + * damage-attribution chart. Convenience for + * `combinedWithAttribution().attributionByValue()`; see + * {@link PMF.attributionByValue}. + */ + attributionByValue() { + return this.combinedWithAttribution().attributionByValue(); + } + /** + * How many of the independent single PMFs can produce the given outcome + * label. Useful for "all of them succeeded" style probabilities where the + * exponent is the number of contributing attacks (see + * {@link DiceQuery.probExactlyK}). + */ + countSinglesWith(label) { + let count = 0; + for (const single of this.singles) { + if (single.hasOutcome(label)) count++; + } + return count; + } + /** + * Returns the expected damage across all possible outcomes. + * + * Example: `query.mean()` → 12.5 + * Use case: "What's my average damage per round?" + */ + mean() { + if (this._combinedProvided) { + let m = 0; + for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; + return m; + } + let totalMean = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; + } + return totalMean; + } + /** + * Returns the variance of the damage distribution. + * + * Example: `query.variance()` → 45.2 + * Use case: "How much does my damage vary from the average?" + * High variance means higher risk/reward. Lower variance means more consistent damage. + */ + variance() { + if (this._combinedProvided) { + const mu = this.mean(); + let v = 0; + for (const [damageValue, bin] of this.combined) { + const dev = damageValue - mu; + v += dev * dev * bin.p; + } + return v; + } + let totalVariance = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + if (Math.abs(mass - 1) <= this._eps) { + totalVariance += single.variance(); + } else { + let mu = 0; + for (const [d2, b] of single) mu += d2 * (b.p / mass); + let v = 0; + for (const [d2, b] of single) { + const dev = d2 - mu; + v += dev * dev * (b.p / mass); + } + totalVariance += v; + } + } + return totalVariance; + } + /** + * Returns the standard deviation of the damage distribution. + * + * Example: `query.stdev()` → 6.7 + * Use case: "What's the typical spread around my average damage?" + * Used to determine how consistent the damage is. + */ + stddev() { + return Math.sqrt(this.variance()); + } + /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ + stdev() { + return this.stddev(); + } + /** + * Returns the Cumulative Distribution Function. + */ + cdf(x) { + return this.probTotalAtMost(x); + } + /** + * Returns the probability of dealing X damage or less. + * In statistics, this is called the cumulative distribution function (CDF). + * Example: `query.cdf(20)` → 0.75 + * Use case: "What's the chance I deal 20 damage or less?" + */ + probTotalAtMost(x) { + let cumulativeProbability = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue <= x) { + cumulativeProbability += probabilityBin.p; + } + } + return cumulativeProbability; + } + /** + * Returns the Complementary Cumulative Distribution Function. + */ + ccdf(x) { + return this.probTotalAtLeast(x); + } + /** + * Returns the probability of dealing at least X damage. + * + * Example: `query.probTotalAtLeast(25)` → 0.35 + * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" + */ + probTotalAtLeast(threshold) { + let probabilitySum = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue >= threshold) { + probabilitySum += probabilityBin.p; + } + } + return probabilitySum; + } + /** + * Returns damage values at specific percentiles. + * + * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] + * Use case: "What are my 25th, 50th, and 75th percentile damage values?" + */ + percentiles(percentileValues) { + const sortedDamageValues = this.combined.support(); + if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); + const cumulativeProbabilities = []; + let runningProbabilitySum = 0; + for (const damageValue of sortedDamageValues) { + runningProbabilitySum += this.combined.map.get(damageValue).p; + cumulativeProbabilities.push(runningProbabilitySum); + } + return percentileValues.map((targetPercentile) => { + let leftBound = 0; + let rightBound = cumulativeProbabilities.length - 1; + while (leftBound <= rightBound) { + const middleIndex = Math.floor((leftBound + rightBound) / 2); + if (cumulativeProbabilities[middleIndex] >= targetPercentile) { + rightBound = middleIndex - 1; + } else { + leftBound = middleIndex + 1; + } + } + return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; + }); + } + /** + * Returns the minimum possible damage. + * + * Example: `query.min()` → 0 + * Use case: "What's the worst-case damage if everything misses?" + */ + min() { + return this.combined.min(); + } + /** + * Returns the maximum possible damage. + * + * Example: `query.max()` → 56 + * Use case: "What's the best-case damage if everything crits and rolls max?" + */ + max() { + return this.combined.max(); + } + singleProb(diceIndex, label) { + const single = this.singles[diceIndex]; + let probabilitySum = 0; + for (const [, probabilityBin] of single) { + probabilitySum += probabilityBin.count[label] || 0; + } + const mass = single.mass(); + return mass > 0 ? probabilitySum / mass : 0; + } + /** + * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it + * carries ANY of `labels`", over the n independent singles. + * + * Each single's per-event success probability is the Poisson-binomial + * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), + * computed exactly once. The binomial DP then runs once to produce the whole + * distribution, so the array-label paths of probExactlyK / probAtLeastK / + * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and + * re-running the DP per requested k. + */ + countDistribution(labels) { + const n = this.singles.length; + const successProbabilities = this.singles.map( + (single) => new _DiceQuery([single]).probabilityOf(labels) + ); + const dist = new Array(n + 1).fill(0); + dist[0] = 1; + for (const successProb of successProbabilities) { + for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { + dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; + } + dist[0] *= 1 - successProb; + } + return dist; + } + probAtLeastK(labels, k) { + const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; + const n = this.singles.length; + if (k <= 0) return 1; + if (k > n) return 0; + const dist = this.countDistribution(L); + let tail = 0; + for (let i = k; i <= n; i++) { + tail += dist[i]; + } + if (tail < 0) return 0; + if (tail > 1) return 1; + return tail; + } + /** + * Returns the probability that at least one attack has the specified outcome(s). + * - This is the complement of probAtMostK(labels, 0) + * + * Examples: + * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) + * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) + * + * Use cases: + * - "What's the chance at least one of my attacks connects?" + * + * Note: + * + * - You have to pass in an array of labels to avoid double-counting if you are + * using multiple labels. You cannot just add them. + */ + probAtLeastOne(labels) { + if (typeof labels === "string") { + labels = [labels]; + } + let productOfNonOccurrence = 1; + for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { + let combinedProbability = 0; + for (const label of labels) { + combinedProbability += this.singleProb(diceIndex, label); + } + if (combinedProbability < 0) combinedProbability = 0; + else if (combinedProbability > 1) combinedProbability = 1; + productOfNonOccurrence *= 1 - combinedProbability; + } + const result = 1 - productOfNonOccurrence; + return result < 0 ? 0 : result > 1 ? 1 : result; + } + /** + * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. + * + * Uses dynamic programming to efficiently calculate the probability distribution + * of how many attacks will have the specified outcome, accounting for different + * success probabilities across individual attacks. + * + * Example: For 3 attacks with 50% hit chance each, returns: + * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] + * + * @param label - The outcome type to count + * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) + * @returns Array where index K contains P(exactly K attacks have the label) + */ + computeBinomialProbabilities(label, maxK) { + const individualProbabilities = this.singles.map( + (_, diceIndex) => this.singleProb(diceIndex, label) + ); + const binomialProbs = new Array(maxK + 1).fill(0); + binomialProbs[0] = 1; + for (const singleProbability of individualProbabilities) { + for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { + binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; + } + binomialProbs[0] *= 1 - singleProbability; + } + return binomialProbs; + } + /** + * Returns the probability that exactly K attacks result in the specified outcome(s). + * + * Single label examples: + * - probExactlyK('hit', 2) = probability exactly 2 attacks hit + * - probExactlyK('crit', 1) = probability exactly 1 attack crits + * - probExactlyK('crit', 0) = probability no attacks crit + * + * Array examples: + * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed + * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds + * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss + * + * Use cases: + * - "What's the chance exactly one of my attacks hits?" + * - "How likely am I to get exactly 2 successes out of 3 attacks?" + * - "What's the probability that exactly half my attacks succeed?" + * + * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. + * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. + */ + probExactlyK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + return probabilityArray[k]; + } + const dist = this.countDistribution(labels); + return k >= 0 && k < dist.length ? dist[k] : 0; + } + /** + * Returns the probability that AT MOST K attacks result in the specified outcome(s). + * + * Single label examples: + * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) + * - probAtMostK('crit', 0) = probability no attacks crit + * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss + * + * Array examples: + * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds + * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) + * + * Use cases: + * - "What's the chance that at most one attack hits?" (rest miss) + * - "How likely am I to have mostly failures?" (at most 1 success) + * - "What's the probability of a really bad turn?" (at most 0 successes) + * + */ + probAtMostK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + let cumulativeSum2 = 0; + for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { + cumulativeSum2 += probabilityArray[outcomeCount]; + } + return cumulativeSum2; + } + const dist = this.countDistribution(labels); + const upper = Math.min(k, dist.length - 1); + let cumulativeSum = 0; + for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { + cumulativeSum += dist[outcomeCount]; + } + return cumulativeSum; + } + /** + * Returns the expected damage attributed to specific outcome types. + * + * Single label examples: + * - expectedDamageFrom('hit') = expected damage from hit components + * - expectedDamageFrom('crit') = expected damage from crit components + * + * Array examples: + * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success + * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses + * + * Use cases: + * - "How much damage do I expect from successful attacks?" + * - "What's the damage contribution from critical hits specifically?" + * - "How much damage comes from miss effects (like save-for-half spells)?" + */ + expectedDamageFrom(labels) { + const wanted = Array.isArray(labels) ? labels : [labels]; + let total = 0; + for (const single of this.singles) { + for (const [dmg, bin] of single) { + let p = 0; + for (const label of wanted) p += bin.count[label] ?? 0; + total += dmg * p; + } + } + return total; + } + /** + * Returns damage statistics for scenarios where AT LEAST ONE attack results in + * the specified outcome(s). + * + * This method answers "What happens when things go reasonably well?" rather than + * "What's the theoretical maximum?" It includes mixed scenarios which are more + * common and tactically relevant than pure scenarios. + * + * Single label examples: + * - damageStatsFrom('hit') = damage range when at least one attack hits + * - damageStatsFrom('crit') = damage range when at least one attack crits + * + * Array examples: + * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds + * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses + * + * Tactical Use Cases: + * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" + * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" + * - "Should I use this risky spell if it has good damage when it works?" + * - "What's my damage potential when something goes right?" (vs pure failure) + * + * Combat Planning Examples: + * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" + * (Much more useful than "You average 50 damage including complete misses") + * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" + * - Resource management: "If I hit anything, I'll likely finish this enemy" + * + * Statistical Note: + * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which + * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. + * + * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an + * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and + * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than + * E[dmg | the label occurs]. For a single attack both are the plain + * conditional figures. Use {@link probAtLeastOne} for the scenario probability. + * + * @example + * // High-level tactical planning + * const successStats = query.damageStatsFrom('hit') + * const successChance = query.probAtLeastOne('hit') + * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) + */ + damageStatsFrom(labels) { + const labelArray = typeof labels === "string" ? [labels] : labels; + let minDamage = Infinity; + let maxDamage = -Infinity; + let totalDamage = 0; + let totalCount = 0; + for (const [damage, probabilityBin] of this.combined) { + let binHasAnyLabel = false; + let binContribution = 0; + for (const label of labelArray) { + const count = probabilityBin.count[label]; + if (count && count > 0) { + binHasAnyLabel = true; + binContribution += count; + } + } + if (damage > 0 && binHasAnyLabel) { + minDamage = Math.min(minDamage, damage); + maxDamage = Math.max(maxDamage, damage); + const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; + totalDamage += damage * weightToUse; + totalCount += weightToUse; + } + } + return { + min: minDamage === Infinity ? 0 : minDamage, + max: maxDamage === -Infinity ? 0 : maxDamage, + avg: totalCount > 0 ? totalDamage / totalCount : 0, + count: totalCount + }; + } + /** + * Returns damage statistics for scenarios where ALL attacks result in the specified + * outcome, calculated by leveraging the pure partition of singles. + * + * This method answers "What's the theoretical best/worst case?" and "What are the + * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. + * + * Examples: + * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) + * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) + * + * UI and Display Use Cases: + * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) + * - "Best case scenario" vs "worst case scenario" analysis + * - Mathematical verification: "Does our hit damage calculation match manual math?" + * - Clean damage type attribution: "How much comes from base hits vs crits?" + * + * Design and Balance Use Cases: + * - Game designers: "What's the damage ceiling if someone gets lucky?" + * - Character optimization: "What's my absolute maximum potential?" + * - Ability comparison: "Which build has higher crit ceiling?" + * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" + * + * Mathematical Use Cases: + * - Validating complex calculations against simple manual math + * - Understanding damage component contributions in isolation + * - Separating luck (crit variance) from consistency (hit variance) + * - Building intuition about damage sources + * + * When to Use This vs damageStatsFrom(): + * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons + * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios + * + * Statistical Note: + * Pure scenarios (all hits, all crits) are rare but represent clear mathematical + * boundaries. These stats help understand the "shape" of your damage potential. + * + * @example + * // UI display logic + * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" + * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" + * + * // vs tactical planning (use damageStatsFrom instead) + * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios + */ + combinedDamageStats(targetLabel) { + const singleStats = this.singles.map( + (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) + ); + if (singleStats.some((stats) => stats.count === 0)) { + return { min: 0, max: 0, avg: 0, count: 0 }; + } + const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); + const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); + const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); + const combinedProb = singleStats.reduce( + (product, stats) => product * stats.count, + 1 + ); + return { + min: combinedMin, + max: combinedMax, + avg: combinedAvg, + count: combinedProb + }; + } + /** + * Returns the probability that at least one attack carries ANY of the + * specified labels (the marginal P(≥1) across the independent attacks). + * + * Examples: + * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) + * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) + * + * Use cases: + * - "What's the chance my resolution includes a success label?" + * - "How likely am I to get any hits or crits across all attacks?" + * + * Note: this must NOT be computed by summing `combined` bin probabilities. A + * single combined damage total is reachable by many outcome combinations and + * a bin can hold several labels at once, so summing `bin.p` over bins that + * contain a label over-counts. The correct marginal is the Poisson-binomial + * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. + */ + probabilityOf(labels) { + return this.probAtLeastOne(labels); + } + /** + * Returns the probability of missing (any type of miss). + * + * Example: `query.missChance()` → 0.04 + * Use case: "What's the chance I miss completely this turn?" + */ + missChance() { + return this.probabilityOf(["missDamage", "missNone"]); + } + /** + * Returns data formatted for plotting damage probability distribution. + * + * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] + * Use case: "I want to visualize my damage distribution in a chart." + */ + toChartSeries() { + return this.combined.support().map((damageValue) => ({ + x: damageValue, + y: this.combined.map.get(damageValue).p + })); + } + /** + * Returns tabular data showing damage values and their probability breakdowns. + * + * Example: `query.toLabeledTable(['hit', 'crit'])` → + * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] + * + * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." + */ + toLabeledTable(labels = []) { + return this.combined.support().map((damageValue) => { + const probabilityBin = this.combined.map.get(damageValue); + const tableRow = { + damage: damageValue, + total: probabilityBin.p + }; + for (const outcomeLabel of labels) { + tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; + } + return tableRow; + }); + } + /** + * Returns data for stacked charts with unconditional per-label probability mass per damage. + * + * - Each dataset value equals the unconditional probability mass for that label at that damage + * (i.e., `bin.count[label]`). + * - Column sums may be less than the total probability `bin.p` when you omit labels or when + * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. + * - This behavior matches tests that expect raw per-label mass (not proportional scaling). + * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. + * + * @example + * query.toStackedChartData(['hit', 'crit']) + * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} + */ + toStackedChartData(labels = [], epsilon = EPS) { + const damageValues = this.combined.support(); + const datasets = labels.map((outcomeLabel) => ({ + label: outcomeLabel, + data: damageValues.map((dmg) => { + const bin = this.combined.map.get(dmg); + const v = bin ? bin.count[outcomeLabel] || 0 : 0; + return v <= epsilon ? 0 : v; + }) + })); + return { labels: damageValues, datasets }; + } + /** + * Returns pure mathematical data for attribution charts showing outcome contributions. + * + * Automatically discovers all outcome types present in the PMF, applies filtering rules, + * and returns proportional data suitable for stacked visualization. + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and proportional data + * + * @example + * query.toAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} + */ + toAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeCount = bin.count[outcome] || 0; + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + const outcomeProbability = bin.p * outcomeFraction; + return asPercentages ? outcomeProbability * 100 : outcomeProbability; + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for damage attribution charts showing damage contribution + * from each outcome type at each damage value. + * + * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of + * bin.count (probability attribution). + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) + * @returns Pure data structure with support, outcomes, and damage attribution data + * + * @example + * query.toDamageAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} + */ + toDamageAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + if (bin.attr) { + for (const outcomeType in bin.attr) { + if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin || !bin.attr) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeDamageAttribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { + if (filterRules(outcomeName, damage)) { + totalDamageAttribution += damageAttr || 0; + } + } + if (totalDamageAttribution === 0) return 0; + const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; + return damagePercentage * bin.p * 100; + } else { + return outcomeDamageAttribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for outcome attribution charts showing which + * attack outcome combinations can produce each damage value. + * + * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks + * outcome combinations - answering "what attack outcomes produced this damage?" + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and outcome combination probabilities + * + * @example + * query.toOutcomeAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} + */ + toOutcomeAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + if (outcome === "missNone") { + const outcomeCount = bin.count[outcome] || 0; + if (outcomeCount === 0) return 0; + if (asPercentages) { + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + return outcomeFraction * bin.p * 100; + } else { + return outcomeCount; + } + } + if (!bin.attr) return 0; + const outcomeDamageContribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [, damageAttr] of Object.entries(bin.attr)) { + totalDamageAttribution += damageAttr || 0; + } + if (totalDamageAttribution === 0) return 0; + const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; + return outcomeFraction * bin.p * 100; + } else { + return outcomeDamageContribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for cumulative distribution function (CDF). + * Shows P(X ≤ x) - the probability of getting at most x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and cumulative probabilities + * + * @example + * query.toCDFSeries() + * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} + */ + toCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const cdfData = []; + for (const damage of support) { + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + cdfData.push( + asPercentages ? cumulativeProbability * 100 : cumulativeProbability + ); + } + return { + support, + data: cdfData + }; + } + /** + * Returns pure mathematical data for complementary cumulative distribution function (CCDF). + * Shows P(X ≥ x) - the probability of getting at least x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and complementary cumulative probabilities + * + * @example + * query.toCCDFSeries() + * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} + */ + toCCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const ccdfData = []; + for (const damage of support) { + const ccdf = 1 - cumulativeProbability; + ccdfData.push(asPercentages ? ccdf * 100 : ccdf); + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + } + return { + support, + data: ccdfData + }; + } + /* + Statistics snapshot of the query. + */ + /** Probability of doing strictly more than threshold damage (default >0). */ + probDamageGreaterThan(threshold = 0) { + let acc = 0; + for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; + return acc; + } + /** All outcome keys actually present (typed & ordered if you pass an order). */ + outcomeKeys(order) { + const found = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) + if (bin.count[k] && bin.count[k] > 0) found.add(k); + } + if (found.size === 0) + ["hit", "crit", "missNone"].forEach((k) => found.add(k)); + const keys = Array.from(found).filter( + (k) => order?.includes(k) ?? true + ); + if (order && order.length) + keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); + return keys; + } + /** Total probability per outcome across the PMF. */ + outcomeTotals(outcomes = this.outcomeKeys()) { + const totals = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => totals.set(o, 0)); + for (const [, row] of this.combined.map) { + for (const o of outcomes) { + const p = row.count[o] || 0; + totals.set(o, (totals.get(o) || 0) + p); + } + } + return totals; + } + /** Conditional damage range per outcome (min/avg/max of X | outcome). */ + outcomeDamageRanges(outcomes = this.outcomeKeys()) { + const table = this.toLabeledTable(outcomes); + const ranges = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); + for (const row of table) { + const dmg = row.damage; + for (const o of outcomes) { + const p = row[o] || 0; + if (p > 0) { + const r = ranges.get(o); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + } + const out = /* @__PURE__ */ new Map(); + for (const o of outcomes) { + const r = ranges.get(o); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); + } + return out; + } + /** + * Snapshot of the distribution in the exact shape the UI consumes. + * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) + * - damageRange is conditional on the outcome occurring + * + * The outcome probabilities use the correct Poisson-binomial marginals + * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), + * so they are always valid probabilities in [0,1]. + * + * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from + * the combined PMF's `count`, which the convolution accumulates as an EXPECTED + * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] + * rather than a clean conditional expectation. It is correct for a single + * attack. + */ + snapshot(order) { + const discovered = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) { + if (bin.count[k] && bin.count[k] > 0) discovered.add(k); + } + } + if (discovered.size === 0) { + for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); + } + let outcomes = Array.from(discovered); + if (order && order.length) { + const inOrder = new Set(order); + outcomes = outcomes.filter((k) => inOrder.has(k)); + const rank = new Map(order.map((k, i) => [k, i])); + outcomes.sort( + (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) + ); + } + const rows = this.toLabeledTable(outcomes); + const rangeAcc = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + rangeAcc.set(ot, { sum: 0, mass: 0 }); + } + for (const row of rows) { + const dmg = row.damage; + for (const ot of outcomes) { + const p = row[ot] || 0; + if (p <= 0) continue; + const r = rangeAcc.get(ot); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + const n = this.singles.length; + const outcomeMap = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + const r = rangeAcc.get(ot); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + outcomeMap.set(ot, { + atLeastOneProbability: this.probAtLeastOne(ot), + allProbability: this.probAtLeastK(ot, n), + damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } + }); + } + const averageDPR = this.mean(); + let damageChance = 0; + for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; + const { support, data } = this.toCDFSeries(false); + const quantile = (p) => { + if (support.length === 0) return 0; + for (let i = 0; i < support.length; i++) + if (data[i] >= p) return support[i]; + return support[support.length - 1]; + }; + const percentiles = { + p25: quantile(0.25), + p50: quantile(0.5), + p75: quantile(0.75) + }; + return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; + } + /** + * PMF Transformation Methods + * + * These methods provide a fluent API for transforming dice queries by wrapping + * the underlying PMF transformation methods. All operations work on the combined + * PMF and return new DiceQuery instances. + */ + /** + * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). + * + * @returns New DiceQuery with normalized combined PMF + */ + normalize() { + return new _DiceQuery([this.combined.normalize()]); + } + /** + * Returns a new DiceQuery with low-probability outcomes removed. + * + * @param eps Minimum probability threshold (defaults to PMF epsilon) + * @param keepFinalBin Whether to keep the highest damage bin regardless of probability + * @returns New DiceQuery with compacted combined PMF + */ + compact(eps, keepFinalBin) { + return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); + } + /** + * Returns a new DiceQuery with an additional scaled branch added. + * Useful for conditional outcomes like "30% chance of opportunity attack". + * + * @param branch DiceQuery to add as a scaled branch + * @param probability Probability of the branch occurring (0-1) + * @returns New DiceQuery combining this query with the scaled branch + * + * @example + * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); + * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); + */ + addScaled(branch, probability) { + return new _DiceQuery([ + this.combined.addScaled(branch.combined, probability) + ]); + } + /** + * Returns a new DiceQuery with all probabilities scaled by a factor. + * Used for conditional scenarios where the entire outcome has reduced probability. + * + * @param factor Scaling factor for probabilities + * @returns New DiceQuery with scaled probabilities + * + * @example + * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario + */ + scaleMass(factor) { + return new _DiceQuery([this.combined.scaleMass(factor)]); + } + totalMass() { + return this.combined.mass(); + } + /** + * Returns a new DiceQuery with damage values transformed by a function. + * Useful for applying modifiers, resistances, or other damage transformations. + * + * @param damageTransformFunction Function to transform each damage value + * @returns New DiceQuery with transformed damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage + * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage + */ + mapDamage(damageTransformFunction) { + return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); + } + /** + * Returns a new DiceQuery with damage values scaled by a factor. + * Convenient wrapper around mapDamage for multiplicative scaling. + * + * @param factor Scaling factor for damage values + * @param rounding Rounding method: "floor" (default), "round", or "ceil" + * @returns New DiceQuery with scaled damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const doubled = baseAttack.scaleDamage(2); // Double damage + * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded + */ + scaleDamage(factor, rounding = "floor") { + return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); + } + /** + * Returns a new DiceQuery combining this query with another via convolution. + * Equivalent to rolling both queries independently and adding results. + * It is important to use this rather than combing()ing the PMFs directly! + * This method maintains the provenance of the PMFs which is needed for damage attribution. + * Combining the .combined PMFs directly is still valid for DPR calculations but + * is not statistically sound for queries. + * + * @param other DiceQuery to combine with + * @param eps Optional epsilon for precision control + * @returns New DiceQuery representing the combined outcome + * + * @example + * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); + * const bothAttacks = mainAttack.convolve(bonusAttack); + */ + convolve(other) { + const singles = [...this.singles, ...other.singles]; + return new _DiceQuery(singles); + } + /** + * First-success split over an ordered list of DISTINCT single-swing PMFs. + * Each PMF may have different success/subset probabilities (from labels). + * + * successOutcome: e.g., ["success"] or ["hit", "crit"] + * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success + * + * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] + */ + firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { + const pmfs = this.singles; + if (!pmfs.length) { + throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); + } + const toArr = (x) => Array.isArray(x) ? x : [x]; + const clamp01 = (x) => Math.max(0, Math.min(1, x)); + const tol = Math.max(eps, 8 * Number.EPSILON); + const per = pmfs.map((pmf) => { + const dq = new _DiceQuery([pmf]); + const pS = dq.probAtLeastOne(toArr(successOutcome)); + const pB = dq.probAtLeastOne(toArr(subsetOutcome)); + if (pB - pS > eps) { + throw new Error( + "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." + ); + } + return { pS, pB }; + }); + let missSoFar = 1; + let pFirstSubset = 0; + let pFirstNonSubset = 0; + let pNone = 1; + for (const { pS, pB } of per) { + pFirstSubset += missSoFar * pB; + pFirstNonSubset += missSoFar * (pS - pB); + const miss = 1 - pS; + missSoFar *= miss; + pNone *= miss; + } + const pAny = 1 - pNone; + const a = clamp01(pFirstNonSubset); + const b = clamp01(pFirstSubset); + const any = clamp01(pAny); + const none = clamp01(pNone); + if (Math.abs(a + b - any) > tol * Math.max(1, any)) { + throw new Error( + `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` + ); + } + return [a, b, any, none]; + } +}; +_DiceQuery.DEFAULT_OUTCOMES = [ + "hit", + "crit", + "missNone" +]; +var DiceQuery = _DiceQuery; +var pmfCache = new LRUCache(1e3); +var _PMF = class _PMF { + constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { + this.map = map; + this.epsilon = epsilon; + this.normalized = normalized; + this.identifier = identifier; + this._preservedProvenance = _preservedProvenance; + } + static empty(epsilon = EPS, identifier = "empty") { + return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); + } + // This has a single bin at value 0, mass of 1 + static zero(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "zero"); + } + static delta(value, epsilon = EPS) { + return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); + } + /** + * Point mass at damage 0 tagged with the canonical `missNone` outcome. + * + * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the + * builder's attack-resolution vocabulary. This uses the `missNone` + * {@link OutcomeType} that the attribution charts and outcome stats key on, + * so it is the correct "clean miss / no damage" delta for provenance-aware + * mixtures feeding those consumers. + */ + static missNone(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "missNone"); + } + // This creates a single bin at value 0, but with weight 0. + static emptyMass() { + return _PMF.zero().scaleMass(0); + } + // Makes PMF iterable over [damage, bin] pairs. + [Symbol.iterator]() { + return this.map[Symbol.iterator](); + } + static clearCache() { + pmfCache.clear(); + } + /** + * Creates a conditional PMF from two branches (success and failure) and a probability. + * This is the core logic for modeling any probabilistic event where there are two + * distinct outcomes. + */ + static branch(successPMF, failurePMF, successProbability) { + let p = successProbability; + if (!Number.isFinite(p)) p = 0; + if (p < 0) p = 0; + if (p > 1) p = 1; + const q = 1 - p; + if (p === 0) return failurePMF.scaleMass(1); + if (p === 1) return successPMF.scaleMass(1); + const eps = successPMF.epsilon ?? failurePMF.epsilon; + const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; + const resultMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of failurePMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); + } + for (const [damageValue, bin] of successPMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); + } + return new _PMF(resultMap, eps, false, id); + } + /** + * withProbability() + * + * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). + * + * Think of this as a shortcut for: + * pmf.gate(p, PMF.zero()) + * + * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, + * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. + * + * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. + * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. + * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) + * use query.firstSuccessSplit() to get the exact probabilities. + * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). + * + */ + static withProbability(successPMF, probability) { + return _PMF.branch(successPMF, _PMF.zero(), probability); + } + /** + * gate() + * + * A conditional wrapper around branch() that applies this PMF with probability `p`, + * and applies a provided fallback PMF otherwise. + * + * This is useful for modeling a binary choice between two outcomes: + * - The "success" outcome (this PMF) happens with probability `p`. + * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. + * + * Examples: + * - 25% chance to include an opportunity attack, otherwise nothing: + * attackPMF.gate(0.25, PMF.zero()) + * + * - 50% chance to deal fireball damage, otherwise cone of cold damage: + * fireballPMF.gate(0.5, coneOfColdPMF) + * + * Relationship to other helpers: + * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. + * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). + * + * @param p Probability of applying this PMF (between 0 and 1). + * @param fallback PMF to apply when this PMF is *not* selected. + * @returns A new PMF representing the weighted mixture of this PMF and the fallback. + */ + gate(p, fallback) { + return _PMF.branch(this, fallback, p); + } + /** + * PMF.exclusive() + * + * Builds a single PMF from a set of mutually exclusive weighted outcomes. + * Exactly one of the provided options will occur. + * + * Each option has: + * - A PMF representing its outcome (e.g., damage dice). + * - A weight representing its probability of being selected. + * + * Notes: + * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for floating point rounding. + */ + static exclusive(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight) || weight < -eps) { + throw new Error(`PMF.exclusive: invalid weight ${weight}.`); + } + } + let totalWeight = items.reduce((s, { weight }) => s + weight, 0); + if (Math.abs(totalWeight) <= eps) totalWeight = 0; + if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; + if (totalWeight > 1 + EPS) { + throw new Error( + `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` + ); + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (weight > eps) out = out.addScaled(pmf, weight); + } + const leftover = Math.max(0, 1 - totalWeight); + if (leftover > eps) { + out = out.addScaled(_PMF.zero(), leftover); + } + return out; + } + /** + * PMF.mix() + * + * Builds a PMF as a linear combination of input PMFs with the given weights. + * Unlike `exclusive`, this does NOT: + * - enforce that weights sum to 1 + * - add leftover probability to δ0 (PMF.zero()) + * + * Use when outcomes are not mutually exclusive, or for interpolation/blending. + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for skipping tiny weights. + */ + static mix(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight)) { + throw new Error(`PMF.mix: invalid weight ${weight}.`); + } + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (Math.abs(weight) <= eps) continue; + out = out.addScaled(pmf, weight); + } + return out; + } + /** + * Adds damage attribution metadata to this PMF based on existing count metadata. + * For each bin, sets attr[outcome] = damage × count[outcome]. + * + * This enables damage attribution charts to work with builder-generated PMFs. + * The parser generates attr automatically, but builder PMFs only have count. + * + * @returns New PMF with attr field populated in each bin + */ + /** + * Returns true if this PMF already carries damage attribution metadata. + * + * Only the first positive-damage bin is inspected (parser-generated PMFs + * populate `attr` uniformly), so this is O(1) in practice. + */ + hasAttribution() { + for (const [damage, bin] of this.map) { + if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { + return true; + } + if (damage > 0) break; + } + return false; + } + withAttribution() { + if (this.hasAttribution()) return this; + const newMap = /* @__PURE__ */ new Map(); + for (const [damage, bin] of this.map) { + const attr = {}; + for (const outcome in bin.count) { + const probability = bin.count[outcome]; + if (probability > 0) { + attr[outcome] = damage * probability; + } + } + newMap.set(damage, { + p: bin.p, + count: { ...bin.count }, + attr: Object.keys(attr).length > 0 ? attr : void 0 + }); + } + return new _PMF( + newMap, + this.epsilon, + this.normalized, + `${this.identifier}~attr` + ); + } + /** + * General-purpose N-way mixture. + * weights: Array of [weight, PMF]. + * + * Example: PMF.mixN([ + * [pMiss, zero], + * [pHit, hitPMF], + * [pCrit, critPMF], + * ]); + */ + static mixN(weights, eps = EPS) { + const filtered = weights.filter(([w]) => w > eps); + if (filtered.length === 0) { + return _PMF.emptyMass(); + } + let acc = null; + let sum = 0; + for (const [w, pmf] of filtered) { + if (acc === null) { + acc = pmf; + sum = w; + } else { + const q = w / (sum + w); + acc = _PMF.branch(pmf, acc, q); + sum += w; + } + } + return acc ?? _PMF.emptyMass(); + } + // This is a convenience method for when we use power + // TODO: It can be smarter in the future, and we can also add it to query + // That way statistics operations on invalid PMFs can throw an error + // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? + setPreservedProvenance(preserved) { + if (!this._preservedProvenance && preserved) { + throw new Error( + "Preserved provenance is already set to false, cannot fix that" + ); + } + this._preservedProvenance = preserved; + } + preservedProvenance() { + return this._preservedProvenance; + } + getPowerCacheKey(n, eps) { + const id = this.identifier; + let key = `${id}`; + for (let i = 1; i < n; i++) key += `+${id}`; + return `${key}@${eps}`; + } + /** + * Efficiently computes this PMF convolved with itself `n` times. + * Uses exponentiation by squaring to reduce total convolutions. + * n must be a positive integer. + * * + * * NOTE: This folds multiple independent attacks into a single PMF. + * As a result, The power() method causes a loss of data provenance. + * This is ONLY SAFE if you are trying to calculate masses. + * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). + */ + power(n, eps = this.epsilon) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("power(n): n must be a positive integer"); + } + if (n === 1) return this; + const epsilon = eps ?? this.epsilon; + const key = this.getPowerCacheKey(n, epsilon); + { + const cached = pmfCache?.get(key); + if (cached) return cached; + } + let base = this.normalized ? this : this.normalize(); + let result = base; + let exp = n - 1; + while (exp > 0) { + if (exp & 1) { + result = result.convolve(base, epsilon); + } + exp >>= 1; + if (exp > 0) { + base = base.convolve(base, epsilon); + } + } + result.setPreservedProvenance(false); + { + pmfCache?.set(key, result); + } + return result; + } + /* + * Helper for chaining multiple identical attacks + */ + replicate(n) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("replicate(n): n must be a positive integer"); + } + if (n === 1) return [this]; + return Array.from({ length: n }, () => this); + } + mass() { + if (this._totalMass === void 0) { + let totalProbabilityMass = 0; + for (const { p } of this.map.values()) { + totalProbabilityMass += p; + } + this._totalMass = totalProbabilityMass; + } + return this._totalMass; + } + outcomeMass(outcome) { + let totalProbabilityMass = 0; + for (const { p, count } of this.map.values()) { + totalProbabilityMass += p * (count[outcome] ?? 0); + } + return totalProbabilityMass; + } + // Helper for testing + faceTotal() { + return [...this.map.keys()].reduce((sum, key) => sum + key, 0); + } + normalize() { + if (this.normalized) return this; + const normalizationFactor = this.mass(); + if (normalizationFactor === 0) return this; + const normalizedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const normalizedCount = {}; + for (const labelKey in probabilityBin.count) { + normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; + } + let normalizedAttributes; + if (probabilityBin.attr) { + normalizedAttributes = {}; + for (const labelKey in probabilityBin.attr) { + normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; + } + } + normalizedMap.set(damageValue, { + p: probabilityBin.p / normalizationFactor, + count: normalizedCount, + attr: normalizedAttributes + }); + } + return new _PMF(normalizedMap, this.epsilon, true, this.identifier); + } + /** + * Returns a copy with negligible probabilities removed (p < eps). + * If keepFinalBin is true, the bin with the largest key is always kept, + * even if its probability is below eps. count/attr submaps are still cleaned. + */ + compact(eps = this.epsilon, keepFinalBin = false) { + let maxKey = -Infinity; + if (keepFinalBin) { + for (const key of this.map.keys()) { + if (key > maxKey) maxKey = key; + } + } + const compactedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; + if (!shouldKeep) continue; + const cleanedBin = _PMF.cloneBin(probabilityBin); + for (const labelKey in cleanedBin.count) { + if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { + delete cleanedBin.count[labelKey]; + } + } + if (cleanedBin.attr) { + for (const labelKey in cleanedBin.attr) { + if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { + delete cleanedBin.attr[labelKey]; + } + } + if (Object.keys(cleanedBin.attr).length === 0) { + cleanedBin.attr = void 0; + } + } + compactedMap.set(damageValue, cleanedBin); + } + return new _PMF(compactedMap, eps, this.normalized, this.identifier); + } + // Note: The "support" of a PMF is the set of all non-zero probability outcomes. + // This returns all damage values with non-zero probability, sorted ascending. + support() { + if (this._support === void 0) { + this._support = [...this.map.keys()].sort((a, b) => a - b); + } + return this._support; + } + // Minimum possible damage value. + min() { + if (this._min === void 0) { + const support = this.support(); + this._min = support.length > 0 ? support[0] : 0; + } + return this._min; + } + // Maximum possible damage value. + max() { + if (this._max === void 0) { + const support = this.support(); + this._max = support.length > 0 ? support[support.length - 1] : 0; + } + return this._max; + } + /** + * Returns the expected (mean) damage value. + * Cached for performance since this requires iterating through all bins. + */ + mean() { + if (this._mean === void 0) { + let totalSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + totalSum += damageValue * probabilityBin.p; + } + this._mean = totalSum; + } + return this._mean; + } + /** + * Returns the variance of the damage distribution. + * Cached for performance since this requires mean calculation plus iteration. + */ + variance() { + if (this._variance === void 0) { + const meanValue = this.mean(); + let varianceSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + const deviationFromMean = damageValue - meanValue; + varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; + } + this._variance = varianceSum; + } + return this._variance; + } + /** + * Returns the standard deviation of the damage distribution. + */ + stdev() { + if (this._stdev === void 0) { + this._stdev = Math.sqrt(this.variance()); + } + return this._stdev; + } + /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ + static cloneBin(bin) { + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ + static scaleBin(bin, factor) { + const count = {}; + for (const k in bin.count) { + count[k] = bin.count[k] * factor; + } + let attr; + if (bin.attr) { + attr = {}; + for (const k in bin.attr) { + attr[k] = bin.attr[k] * factor; + } + } + return { p: bin.p * factor, count, attr }; + } + static mergeInto(destinationMap, damageValue, binToAdd) { + const existingBin = destinationMap.get(damageValue); + if (!existingBin) { + destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); + return; + } + existingBin.p += binToAdd.p; + for (const labelKey in binToAdd.count) { + existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; + } + if (binToAdd.attr) { + if (!existingBin.attr) { + existingBin.attr = {}; + } + for (const labelKey in binToAdd.attr) { + existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; + } + } + } + // Convenience method + add(other) { + return this.addScaled(other, 1); + } + /** + * Returns a new PMF with a scaled branch added to this one. + * The branch PMF is scaled by the given probability before merging + * This will be very useful for conditional effects and for being + * able to model "I can probably have this opportunity attack 40% of rounds" + * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes + */ + addScaled(branch, probability) { + if (probability === 0) return this; + const resultMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of this.map) { + resultMap.set(dmg, _PMF.cloneBin(bin)); + } + for (const [damageValue, probabilityBin] of branch.map) { + _PMF.mergeInto( + resultMap, + damageValue, + _PMF.scaleBin(probabilityBin, probability) + ); + } + return new _PMF( + resultMap, + this.epsilon, + false, + `${this.identifier}+scaled(${branch.identifier},${probability})` + ); + } + /** + * Redistributes probability mass to model an effect that only occurs with + * probability `frequency` — a conditional attack, an on-hit rider, or a + * sub-one AoE target fraction. + * + * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, + * per-label `count`, AND per-label `attr` — and the freed mass is moved into + * the miss bin at damage 0, tagged with the canonical `missNone` outcome. + * Total probability mass is preserved. + * + * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage + * attribution (`attr`) intact, so a frequency-scaled PMF still renders + * correctly in the damage-attribution charts. + * + * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` + * collapses all mass into the miss bin. The miss outcome is assumed to be + * encoded at damage value 0. + * + * @param frequency Probability in [0, 1] that the effect occurs. + */ + applyHitFrequency(frequency) { + if (!Number.isFinite(frequency) || frequency >= 1) return this; + const freq = Math.max(0, frequency); + const pMiss = this.pAt(0); + const pHit = 1 - pMiss; + const newMissMass = pMiss + (1 - freq) * pHit; + const newMap = /* @__PURE__ */ new Map(); + newMap.set(0, { + p: newMissMass, + count: { [MISS_NONE_OUTCOME]: newMissMass }, + attr: {} + }); + for (const [damage, bin] of this.map) { + if (damage <= 0) continue; + newMap.set(damage, _PMF.scaleBin(bin, freq)); + } + return new _PMF( + newMap, + this.epsilon, + false, + `freq(${this.identifier},${freq})` + ); + } + scaleMass(factor) { + if (factor === 1) return this; + const scaledMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); + } + return new _PMF( + scaledMap, + this.epsilon, + false, + `scale(${this.identifier},${factor})` + ); + } + mapDamage(damageTransformFunction) { + const transformedMap = /* @__PURE__ */ new Map(); + for (const [originalDamage, probabilityBin] of this.map) { + const transformedDamage = damageTransformFunction(originalDamage); + _PMF.mergeInto( + transformedMap, + transformedDamage, + _PMF.cloneBin(probabilityBin) + ); + } + return new _PMF( + transformedMap, + this.epsilon, + this.normalized, + `map(${this.identifier})` + ); + } + scaleDamage(factor, rounding = "floor") { + const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; + return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); + } + getPMFCombineCacheKey(p1, p2, eps, raw) { + const [id1, id2] = [p1.identifier, p2.identifier].sort(); + return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; + } + /** + * A small content fingerprint (mass + bin count + face sum) so convolution + * cache keys change if the underlying numbers do. Memoized because a PMF is + * immutable once constructed — this avoids re-summing every key on each + * convolve() call (including cache hits). + */ + fingerprint() { + if (this._fingerprint === void 0) { + let faceSum = 0; + for (const k of this.map.keys()) faceSum += k; + this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; + } + return this._fingerprint; + } + convolve(other, eps, raw = false) { + const epsilon = eps ?? this.epsilon; + const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); + const A0 = norm(this); + const B0 = norm(other); + const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; + const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); + const cached = pmfCache?.get(cacheKey); + if (cached) return cached; + const combinedMap = /* @__PURE__ */ new Map(); + for (const [aVal, aBin] of A.map) { + const ap = aBin.p; + const aCount = aBin.count; + const aAttr = aBin.attr; + for (const [bVal, bBin] of B.map) { + const bp = bBin.p; + const dmg = aVal + bVal; + let dest = combinedMap.get(dmg); + if (dest === void 0) { + dest = { p: 0, count: {} }; + combinedMap.set(dmg, dest); + } + dest.p += ap * bp; + const dc = dest.count; + for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; + for (const k in bBin.count) + dc[k] = (dc[k] || 0) + bBin.count[k] * ap; + if (aAttr || bBin.attr) { + let da = dest.attr; + if (da === void 0) { + da = {}; + dest.attr = da; + } + if (aAttr) + for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; + if (bBin.attr) + for (const k in bBin.attr) + da[k] = (da[k] || 0) + bBin.attr[k] * ap; + } + } + } + let result = new _PMF( + combinedMap, + epsilon, + !raw, + `${A.identifier}${raw ? "*" : "+"}${B.identifier}` + ); + const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); + const mGot = result.mass(); + if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { + result = result.scaleMass(mExp / mGot); + } + if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) + result = result.normalize(); + pmfCache?.set(cacheKey, result); + return result; + } + // 3) Nice wrapper so you can call pmf.combineRaw(other) + combineRaw(other, eps) { + return this.convolve(other, eps, true); + } + // Reduce a list of PMFs by left-folding convolve() with the given eps + static reduceConvolveLeft(pmfList, eps) { + let result = pmfList[0]; + for (let i = 1; i < pmfList.length; i++) { + result = result.convolve(pmfList[i], eps); + } + return result; + } + /** + * Convolves multiple PMFs using linear convolution with automatic caching. + * Uses a left-to-right accumulation approach for maximum cache reuse. + * Each convolve() call automatically uses the convolution cache for performance. + * + * This linear approach provides better cache hits than pairwise because: + * - Intermediate results are more predictable and stable + * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) + * - Order-independent cache keys work better with consistent build patterns + */ + static convolveMany(pmfList, eps = EPS) { + if (pmfList.length === 0) return _PMF.empty(eps); + if (pmfList.length === 1) return pmfList[0]; + return _PMF.reduceConvolveLeft(pmfList, eps); + } + /** + * Returns a plain, JSON-serializable representation of this PMF. + * + * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces + * the expected output (no double-encoding). Use {@link PMF.fromJSON} to + * reconstruct, or {@link PMF.toJSONString} if you need the string directly. + */ + toJSON() { + return { + bins: [...this.map.entries()], + normalized: this.normalized, + identifier: this.identifier + }; + } + /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ + toJSONString() { + return JSON.stringify(this); + } + static fromJSON(jsonData) { + return new _PMF( + new Map(jsonData.bins), + EPS, + !!jsonData.normalized, + jsonData.identifier || "fromJSON" + ); + } + /** + * Relative pruning with optional top-K floor. + * Keeps bins with p >= epsRel * peak, always keeps min and max damage, + * optionally guarantees at least `minBins` survivors by adding top-K. + * Returns a new, non-normalized PMF. + */ + prune(epsRel, minBins = 0) { + const size = this.map.size; + if (size === 0) return this; + let peak = 0; + let minDamage = Number.POSITIVE_INFINITY; + let maxDamage = Number.NEGATIVE_INFINITY; + for (const [dmg, bin] of this.map) { + if (bin.p > peak) peak = bin.p; + if (dmg < minDamage) minDamage = dmg; + if (dmg > maxDamage) maxDamage = dmg; + } + if (peak === 0) + return new _PMF(new Map(this.map), epsRel, false, this.identifier); + const thresh = epsRel * peak; + const entries = [...this.map.entries()]; + const survivorsByDmg = /* @__PURE__ */ new Map(); + const protect = (d2) => { + const b = this.map.get(d2); + if (b) survivorsByDmg.set(d2, b); + }; + protect(minDamage); + if (maxDamage !== minDamage) protect(maxDamage); + for (const [dmg, bin] of entries) { + if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); + } + if (minBins > 0 && survivorsByDmg.size < minBins) { + entries.sort((a, b) => b[1].p - a[1].p); + for (const [dmg, bin] of entries) { + if (!survivorsByDmg.has(dmg)) { + survivorsByDmg.set(dmg, bin); + if (survivorsByDmg.size >= minBins) break; + } + } + } + const prunedMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of survivorsByDmg) { + const newCount = {}; + for (const k in bin.count) { + const v = bin.count[k]; + if (Math.abs(v) >= thresh) newCount[k] = v; + } + let newAttr; + if (bin.attr) { + for (const k in bin.attr) { + const v = bin.attr[k]; + if (Math.abs(v) >= thresh) { + if (!newAttr) newAttr = {}; + newAttr[k] = v; + } + } + } + prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); + } + return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); + } + /** Probability mass at exactly x. */ + pAt(x) { + return this.map.get(x)?.p ?? 0; + } + /** + * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. + * Assumes a miss is encoded as the damage-0 bin (the convention used across + * attack/save PMFs). The dual of {@link missProbability}. + */ + hitProbability() { + return 1 - this.pAt(0); + } + /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ + missProbability() { + return this.pAt(0); + } + /** + * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width + * damage buckets, aggregating probability mass (and `count`/`attr` + * provenance) into each bucket's start value. Returns this PMF unchanged when + * its integer support already fits within `maxBuckets`. + * + * This is a lossy display/downsampling transform (bucket start replaces the + * exact damage value) — use it for charting wide distributions, not for DPR + * math. + */ + rebin(maxBuckets) { + if (!(maxBuckets > 0)) return this; + const support = this.support(); + if (support.length === 0) return this; + const min = support[0]; + const max = support[support.length - 1]; + const range = max - min; + if (range + 1 <= maxBuckets) return this; + const binSize = Math.ceil((range + 1) / maxBuckets); + return this.mapDamage((d2) => min + Math.floor((d2 - min) / binSize) * binSize); + } + /** Dense integer support from min..max (inclusive). + * Useful for showing empty bars in charts. + */ + denseSupport() { + const s = this.support(); + if (s.length === 0) return []; + const lo = Math.min(...s), hi = Math.max(...s); + return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( + (a, b) => a - b + ); + } + /** CDF at x: P(X ≤ x). */ + cdfAt(x) { + let acc = 0; + for (const [val, bin] of this.map) if (val <= x) acc += bin.p; + return acc; + } + /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ + quantile(p) { + if (this.map.size === 0) return 0; + const s = this.support().sort((a, b) => a - b); + let acc = 0; + for (const x of s) { + acc += this.pAt(x); + if (acc >= p) return x; + } + return s[s.length - 1]; + } + /** Get outcome probability at specific damage value. */ + outcomeAt(damage, outcome) { + return this.map.get(damage)?.count[outcome] ?? 0; + } + /** Get all outcome types present in this PMF. */ + outcomes() { + const outcomeSet = /* @__PURE__ */ new Set(); + for (const [, bin] of this.map) { + for (const outcome in bin.count) { + if (bin.count[outcome] > 0) { + outcomeSet.add(outcome); + } + } + } + return Array.from(outcomeSet).sort(); + } + /** Get total probability of an outcome across all damage values. */ + outcomeProbability(outcome) { + let total = 0; + for (const [, bin] of this.map) { + total += bin.count[outcome] ?? 0; + } + return total; + } + /** Get damage attribution for an outcome at specific damage value. */ + outcomeAttributionAt(damage, outcome) { + return this.map.get(damage)?.attr?.[outcome] ?? 0; + } + /** Get all outcome data at specific damage value. */ + binAt(damage) { + const bin = this.map.get(damage); + if (!bin) return null; + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Check if outcome exists in this PMF. */ + hasOutcome(outcome) { + for (const [, bin] of this.map) { + if ((bin.count[outcome] ?? 0) > 0) { + return true; + } + } + return false; + } + /** + * Split each damage value's probability mass across outcome labels, returning + * per-label maps of `damage value → probability mass attributable to that + * label`. Summing over labels at a given value recovers that value's `p`. + * + * Damage-bearing bins are split by `attr` weight (the share of damage each + * outcome contributed); the clean-miss bin at 0 is split by `count` weight + * (there is no damage to attribute). Attribution is computed on demand via + * {@link withAttribution} when absent, so builder-generated PMFs work too. + * + * This is the provenance core of the stacked damage-attribution chart — the + * caller only maps these series into its rendering format (colors, binning, + * axis labels). + */ + attributionByValue() { + const src = this.hasAttribution() ? this : this.withAttribution(); + const result = /* @__PURE__ */ new Map(); + const add = (label, damage, mass) => { + if (!(mass > 0)) return; + let series = result.get(label); + if (!series) { + series = /* @__PURE__ */ new Map(); + result.set(label, series); + } + series.set(damage, (series.get(damage) ?? 0) + mass); + }; + for (const [damage, bin] of src.map) { + const p = bin.p || 0; + if (p <= 0) continue; + const isMissBin = damage === 0; + if (isMissBin) { + let totalCount = 0; + for (const k in bin.count) totalCount += bin.count[k] || 0; + if (totalCount > 0) { + const c = bin.count[MISS_NONE_OUTCOME] || 0; + add(MISS_NONE_OUTCOME, damage, c / totalCount * p); + } + continue; + } + let totalAttr = 0; + if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; + if (bin.attr && totalAttr > 0) { + for (const k in bin.attr) { + if (k === MISS_NONE_OUTCOME) continue; + add(k, damage, (bin.attr[k] || 0) / totalAttr * p); + } + } + } + return result; + } + tailProbGE(t) { + let s = 0; + for (const [x, bin] of this) { + if (bin.p > 0 && x >= t) s += bin.p; + } + return s; + } + tailProbGT(t) { + let s = 0; + for (const [x, rec] of this) { + if (x > t) s += rec.p; + } + return s; + } + /** + * Returns a new PMF containing only bins where the specified outcome has non-zero probability. + * This creates a marginal distribution for the given outcome type, with probabilities + * scaled to represent the unconditional mass attributable to that outcome. + */ + filterOutcome(outcome) { + const filteredMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of this.map) { + const outcomeCount = bin.count[outcome] ?? 0; + const totalCount = Object.values(bin.count ?? {}).reduce( + (a, b) => (a ?? 0) + (b ?? 0), + 0 + ); + if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { + const proportion = outcomeCount / totalCount; + const newP = bin.p * proportion; + const newCount = { [outcome]: outcomeCount }; + let newAttr; + if (bin.attr && bin.attr[outcome] !== void 0) { + newAttr = { [outcome]: bin.attr[outcome] * proportion }; + } + filteredMap.set(damageValue, { + p: newP, + count: newCount, + attr: newAttr + }); + } + } + return new _PMF( + filteredMap, + this.epsilon, + false, + // don't normalize by default + `filter(${this.identifier},${outcome})` + ); + } + /** + * Calculates probabilities for first-success outcomes across n independent attempts. + * + * @param pSuccess - Total probability of any success on a single attempt. + * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). + * @param n - Number of independent attempts. + * + * Returns: + * - pSpecificSuccess: Probability that the first success was of the "special" type + * - pGeneralSuccess: Probability that the first success was of the non-special type + * - pNone: Probability that no successes occurred + * - pAny: Probability that at least one success occurred + */ + static firstSuccessWeights(pSuccess, pSpecial, n) { + if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { + throw new Error( + `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` + ); + } + const pFail = 1 - pSuccess; + const pFailAll = Math.pow(pFail, n); + const pAny = 1 - pFailAll; + const denom = pSuccess === 0 ? 1 : pSuccess; + const pSpecificSuccess = pSpecial * pAny / denom; + const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; + const pNone = 1 - pSpecificSuccess - pGeneralSuccess; + return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; + } + mapValues(f, eps = EPS, opts) { + const rounding = opts?.rounding ?? "none"; + const preserveCounts = opts?.preserveCounts ?? true; + const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; + const probs = /* @__PURE__ */ new Map(); + const counts = /* @__PURE__ */ new Map(); + for (const [v, bin] of this) { + if (Math.abs(bin.p) < eps) continue; + const u = round(f(v)); + probs.set(u, (probs.get(u) ?? 0) + bin.p); + if (preserveCounts) { + const src = bin.count; + if (src) { + const dest = counts.get(u) ?? {}; + for (const k in src) { + dest[k] = (dest[k] ?? 0) + src[k]; + } + counts.set(u, dest); + } + } + } + const internal = /* @__PURE__ */ new Map(); + for (const [u, p] of probs) { + internal.set(u, { p, count: counts.get(u) ?? {} }); + } + return _PMF.fromMap( + new Map(Array.from(internal, ([u, b]) => [u, b.p])), + eps + ); + } + static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { + const filtered = []; + for (const [v, p] of m) { + if (!Number.isFinite(v) || !Number.isFinite(p)) continue; + if (p <= 0 || Math.abs(p) < eps) continue; + if (requireIntegerValues && !Number.isInteger(v)) { + throw new Error(`fromMap: non-integer outcome ${v}`); + } + filtered.push([v, p]); + } + if (filtered.length === 0) { + throw new Error("fromMap: empty or invalid input map"); + } + let sum = 0; + let c = 0; + for (const [, p] of filtered) { + const y = p - c; + const t = sum + y; + c = t - sum - y; + sum = t; + } + if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); + filtered.sort((a, b) => a[0] - b[0]); + const internal = /* @__PURE__ */ new Map(); + for (const [v, p] of filtered) { + internal.set(v, { p: p / sum, count: {} }); + } + return new _PMF(internal, eps); + } + query() { + return new DiceQuery(this); + } +}; +// Unique ID generator for anonymous PMFs to avoid cache key collisions +_PMF.__anonIdCounter = 1; +var PMF = _PMF; + +// src/pmf/mixture.ts +var Mixture = class _Mixture { + constructor(eps = EPS) { + this.totals = /* @__PURE__ */ new Map(); + // raw mass per outcome (pre-normalization) + this.labelMass = /* @__PURE__ */ new Map(); + this.eps = Number.isFinite(eps) ? eps : EPS; + } + /** Remove all accumulated state. */ + clear() { + this.totals.clear(); + this.labelMass.clear(); + return this; + } + /** Number of distinct outcome values currently accumulated. */ + size() { + return this.totals.size; + } + /** Whether a label was ever added. */ + hasLabel(label) { + for (const bag of this.labelMass.values()) if (bag[label]) return true; + return false; + } + /** + * Add a labeled component with a mixture weight. + * Weight can be any positive finite number. Very small contributions are pruned by eps. + */ + add(label, pmf, weight = 1) { + if (!Number.isFinite(weight) || weight <= 0) return this; + for (const [v, bin] of pmf) { + const p = bin.p; + if (p <= 0) continue; + const add = weight * p; + if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; + this.totals.set(v, (this.totals.get(v) ?? 0) + add); + const bag = this.labelMass.get(v) ?? {}; + bag[label] = (bag[label] ?? 0) + add; + this.labelMass.set(v, bag); + } + return this; + } + buildPMF(eps = EPS) { + let grand = 0; + let c = 0; + for (const m of this.totals.values()) { + const y = m - c; + const t = grand + y; + c = t - grand - y; + grand = t; + } + if (!(grand > 0)) throw new Error("Mixture: zero total mass"); + const internal = /* @__PURE__ */ new Map(); + for (const [v, m] of this.totals) { + if (m <= 0 || Math.abs(m) < this.eps) continue; + const count = this.labelMass.get(v) ?? {}; + internal.set(v, { p: m / grand, count }); + } + return new PMF(internal, eps); + } + /** + * Produce normalized *per-label* PMFs (labels independent). + * These are unlabeled PMFs built from the raw mass of that label alone. + */ + byOutcome() { + const labels = /* @__PURE__ */ new Set(); + for (const bag of this.labelMass.values()) { + for (const k of Object.keys(bag)) labels.add(k); + } + const out = {}; + for (const label of labels) { + const m = /* @__PURE__ */ new Map(); + for (const [v, bag] of this.labelMass) { + const w = bag[label]; + if (w && Math.abs(w) >= this.eps) m.set(v, w); + } + if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); + } + return out; + } + /** + * Mixture weights per label, normalized to sum to 1 over labels that appeared. + * Uses raw mass before per-outcome normalization. + */ + weights() { + const res = {}; + for (const [, bag] of this.labelMass) { + for (const [lab, w] of Object.entries(bag)) { + if (!Number.isFinite(w) || w <= 0) continue; + res[lab] = (res[lab] ?? 0) + w; + } + } + let total = 0; + let c = 0; + for (const v of Object.values(res)) { + const y = v - c; + const t = total + y; + c = t - total - y; + total = t; + } + if (total > 0) { + for (const k in res) res[k] = res[k] / total; + } + return res; + } + toJSON() { + return { + totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), + labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), + eps: this.eps + }; + } + static mix(items, eps = EPS) { + const mix = new _Mixture(eps); + for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); + return mix.buildPMF(); + } +}; + +// src/common/errors.ts +var DiceParseError = class _DiceParseError extends Error { + constructor(message, options) { + super(message); + this.name = "DiceParseError"; + this.expression = options?.expression; + this.cause = options?.cause; + Object.setPrototypeOf(this, _DiceParseError.prototype); + } +}; + +// src/parser/dice.ts +var MAX_BINARY_OUTCOMES = 1e8; +var Dice = class _Dice { + constructor(x = 0) { + this.faces = {}; + this.privateData = {}; + // Partial: the object starts empty and gains keys as outcomes are recorded, + // so the type must not claim every OutcomeType is present. (Previously typed + // as a full Record via an `as` cast, which lied about missing keys.) + this.outcomeData = {}; + this.hasHitDistributionCalculated = false; + if (x <= 0) return; + for (let i = 1; i <= x; i++) { + this.faces[i] = 1; + } + } + getOutcomeDistribution(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const distribution = this.outcomeData[key]; + if (distribution === void 0) return void 0; + return { ...distribution }; + } + getFullOutcomeDistribution() { + return { ...this.outcomeData }; + } + setOutcomeDistribution(key, data) { + if (data) { + this.outcomeData[key] = data; + } else { + delete this.outcomeData[key]; + } + } + hasOutcomeData(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const data = this.outcomeData[key]; + return data !== void 0 && Object.keys(data).length > 0; + } + getOutcomeCount(key, face) { + return this.outcomeData[key]?.[face] ?? 0; + } + getAverage(key) { + const distribution = this.getOutcomeDistribution(key); + if (!distribution) return 0; + const totalCount = Object.values(distribution).reduce( + (sum, count) => sum + count, + 0 + ); + const expectedDamage = Object.entries(distribution).reduce( + (sum, [damage, count]) => sum + Number(damage) * count, + 0 + ); + if (totalCount === 0) return 0; + return expectedDamage / totalCount; + } + // TODO this can be private later if we change how testing works + calculateHitDistribution() { + const hitValues = {}; + const subtractedOutcomes = [ + this.outcomeData.crit, + this.outcomeData.missNone, + this.outcomeData.missDamage, + this.outcomeData.saveHalf, + this.outcomeData.saveFail, + this.outcomeData.pc + ]; + for (const [face, totalCount] of Object.entries(this.faces)) { + const numFace = Number(face); + let hitCount = totalCount; + for (const distribution of subtractedOutcomes) { + const outcomeCount = distribution?.[numFace]; + if (outcomeCount) { + hitCount -= outcomeCount; + } + } + if (numFace === 0) { + hitCount = 0; + } + if (hitCount < 0) { + hitCount = 0; + } + hitValues[numFace] = hitCount; + } + return hitValues; + } + ensureHitDistribution() { + if (!this.hasHitDistributionCalculated) { + const hitValues = this.calculateHitDistribution(); + this.setOutcomeDistribution("hit", hitValues); + this.hasHitDistributionCalculated = true; + } + } + // PRIVATE FUNCTIONS + binaryOp(other, op, diceConstructor) { + const result = diceConstructor ? diceConstructor() : new _Dice(); + const isScalar = typeof other === "number"; + const keys1 = this.keys(); + const keys2 = isScalar ? [] : other.keys(); + if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { + throw new DiceParseError( + `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` + ); + } + for (const key1 of keys1) { + const value1 = this.faces[key1]; + if (isScalar) { + const resultKey = op(key1, other); + result.increment(resultKey, value1); + } else { + for (const key2 of keys2) { + const value2 = other.faces[key2]; + const resultKey = op(key1, key2); + result.increment(resultKey, value1 * value2); + } + } + } + return result; + } + removeFaces(facesToRemove) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (!facesToRemove.includes(numKey)) { + result.faces[numKey] = value; + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // PUBLIC FUNCTIONS + getFaceEntries() { + return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); + } + getFaceMap() { + return { ...this.faces }; + } + get(face) { + return this.faces[face] ?? 0; + } + keys() { + return Object.keys(this.faces).map(Number); + } + values() { + return Object.values(this.faces); + } + total() { + return Object.values(this.faces).reduce((sum, value) => sum + value, 0); + } + setFace(key, value) { + this.faces[key] = value; + } + static scalar(value) { + const result = new _Dice(); + result.increment(value, 1); + return result; + } + maxFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.max(...numericKeys); + } + minFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.min(...numericKeys); + } + increment(face, count) { + const current = this.faces[face] || 0; + this.faces[face] = current + count; + } + normalize(scalar) { + const result = new _Dice(); + for (const [face, count] of Object.entries(this.faces)) { + result.faces[Number(face)] = count * scalar; + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // OPERATIONS + add(other) { + return this.binaryOp(other, (a, b) => a + b); + } + subtract(other) { + return this.binaryOp(other, (a, b) => a - b); + } + conditionalApply(other) { + return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); + } + multiply(other) { + return this.binaryOp(other, (a, b) => a * b); + } + addNonZero(other) { + return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); + } + eq(other) { + return this.binaryOp(other, (a, b) => a === b ? 1 : 0); + } + max(other) { + return this.binaryOp(other, (a, b) => Math.max(a, b)); + } + min(other) { + return this.binaryOp(other, (a, b) => Math.min(a, b)); + } + advantage() { + return this.max(this); + } + ge(other) { + return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); + } + divide(other) { + return this.binaryOp(other, (a, b) => a / b); + } + divideRoundUp(other) { + return this.binaryOp(other, (a, b) => Math.ceil(a / b)); + } + divideRoundDown(other) { + return this.binaryOp(other, (a, b) => Math.floor(a / b)); + } + and(other) { + return this.binaryOp(other, (a, b) => a && b ? 1 : 0); + } + checkTarget(other, comparisonLogic) { + const createResult = () => { + const result = new _Dice(); + result.increment(0, 0); + result.increment(1, 0); + return result; + }; + return this.binaryOp(other, comparisonLogic, createResult); + } + dc(other) { + const dcCheck = (a, b) => a >= b ? 0 : 1; + const result = this.checkTarget(other, dcCheck); + result.privateData.isDCCheck = true; + return result; + } + ac(other) { + const acCheck = (a, b) => a >= b ? a : 0; + return this.checkTarget(other, acCheck); + } + deleteFace(face) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (numKey !== face) { + result.increment(numKey, value); + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + reroll(toReroll) { + const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; + const rerollKeys = rerollDice.keys(); + const rerollSet = new Set(rerollKeys); + const removed = this.removeFaces(rerollKeys); + let result = new _Dice(); + for (const face of this.keys()) { + const wasRerolled = rerollSet.has(face); + result = result.combine(removed); + if (wasRerolled) { + result = result.combine(this); + } + } + return result; + } + // This is not addition and not rolling two dice at once. + // Instead, it’s mixing two distributions into a single weighted die. + combine(other) { + if (typeof other === "number") { + other = _Dice.scalar(other); + } + const result = new _Dice(); + for (const [key, value] of Object.entries(other.faces)) { + result.faces[Number(key)] = value; + } + const except = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + result.increment(numKey, value); + if (!(numKey in other.faces)) { + except.increment(numKey, value); + } + } + result.privateData = { ...this.privateData, except: other }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + combineInPlace(other) { + for (const [key, value] of Object.entries(other.faces)) { + const numKey = Number(key); + const current = this.faces[numKey] || 0; + this.faces[numKey] = current + value; + } + } + percent() { + const total = this.total(); + const result = {}; + for (const [face, count] of Object.entries(this.faces)) { + result[Number(face)] = count / total; + } + return result; + } + average() { + const total = this.total(); + if (total === 0) return 0; + let sum = 0; + for (const [key, value] of Object.entries(this.faces)) { + sum += Number(key) * value; + } + return sum / total; + } + /* + * Convert dice to PMF using OutcomeType labels directly from damage distribution. + * This is much cleaner than the original complex distribution conversion. + */ + toPMF(numEpsilon = EPS) { + const total = this.total(); + if (total === 0) return PMF.empty(numEpsilon); + this.ensureHitDistribution(); + const map = /* @__PURE__ */ new Map(); + const hitDistro = this.getOutcomeDistribution("hit") || {}; + const critDistro = this.getOutcomeDistribution("crit") || {}; + const missDistro = this.getOutcomeDistribution("missDamage") || {}; + const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; + const pcDistro = this.getOutcomeDistribution("pc") || {}; + const isSaveHalf = Object.keys(saveDistro).length > 0; + const isDCCheck = this.privateData.isDCCheck === true; + const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; + for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { + const face = Number(faceStr); + const faceCount = Number(faceCountRaw); + if (faceCount <= 0) continue; + let p = faceCount / total; + p = clampNonNeg(p); + if (!(p > 0)) continue; + if (numEpsilon >= 0 && p < numEpsilon) continue; + const count = {}; + const attr = {}; + if (hitDistro[face]) { + const c = clampNonNeg(hitDistro[face] / total); + if (c > 0) { + if (isSaveHalf || isDCCheck) { + count.saveFail = c; + attr.saveFail = clampNonNeg(face * hitDistro[face] / total); + } else { + count.hit = c; + attr.hit = clampNonNeg(face * hitDistro[face] / total); + } + } + } + if (critDistro[face]) { + const c = clampNonNeg(critDistro[face] / total); + if (c > 0) { + count.crit = c; + attr.crit = clampNonNeg(face * critDistro[face] / total); + } + } + if (missDistro[face]) { + const c = clampNonNeg(missDistro[face] / total); + if (c > 0) { + count.missDamage = c; + attr.missDamage = clampNonNeg(face * missDistro[face] / total); + } + } + if (saveDistro[face]) { + const c = clampNonNeg(saveDistro[face] / total); + if (c > 0) { + if (isSaveHalf) { + count.saveHalf = c; + attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); + } else { + count.saveFail = (count.saveFail ?? 0) + c; + attr.saveFail = clampNonNeg( + (attr.saveFail ?? 0) + face * saveDistro[face] / total + ); + } + } + } + if (pcDistro[face]) { + const c = clampNonNeg(pcDistro[face] / total); + if (c > 0) { + count.pc = c; + attr.pc = clampNonNeg(face * pcDistro[face] / total); + } + } + if (!isSaveHalf && !isDCCheck) { + const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); + const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); + if (unaccountedCount > 0) { + const frac = clampNonNeg(unaccountedCount / total); + if (frac > 0) { + count.missNone = (count.missNone ?? 0) + frac; + } + } + } + const bin = { p, count }; + if (Object.keys(attr).length > 0) { + bin.attr = attr; + } + map.set(face, bin); + } + const identifier = this.identifier || "ERROR"; + return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); + } +}; + +// src/parser/parser.ts +var MAX_DIE_SIDES = 1e6; +var MAX_DICE_COUNT = 1e4; +var MAX_KEEP_OUTCOMES = 1e6; +var parseCache = new LRUCache(1e3); +function parse(expression, n = 0) { + const cleaned = expression.replace(/ /g, "").toLowerCase(); + { + const cacheKey = `${cleaned}:${n}`; + const cached = parseCache.get(cacheKey); + if (cached) return cached; + } + const chars = [...cleaned]; + let result; + try { + result = parseExpression(chars, n); + } catch (error) { + throw new DiceParseError( + `Cannot parse dice expression [${expression}]: ${error}`, + { expression, cause: error } + ); + } + result.privateData = result.privateData || {}; + result.identifier = cleaned; + if (chars.length > 0) { + throw new DiceParseError( + `Unexpected token: '${chars[0]}' from expression: '${expression}'`, + { expression } + ); + } + const resultPMF = result.toPMF(-1); + { + const cacheKey = `${cleaned}:${n}`; + parseCache.set(cacheKey, resultPMF); + } + return resultPMF; +} +function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { + dice = dice.normalize(currentNorm); + finalResult = finalResult.normalize(normValue); + finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); + finalResult = finalResult.combine(dice); + return { newNorm: currentNorm * normValue, updatedResult: finalResult }; +} +function parseExpression(arr, n) { + const result = (() => { + const res = parseArgument(arr, n); + return typeof res === "number" ? Dice.scalar(res) : res; + })(); + let op = parseOperation(arr); + let finalResult = result; + while (op != null) { + const arg = !op.unary ? parseArgument(arr, n) : finalResult; + let crit; + let critNorm = 1; + if (arr[0] === "x" || arr[0] === "c") { + const isXcrit = arr[0] === "x"; + if (isXcrit) assertToken(arr, "x"); + assertToken(arr, "c"); + assertToken(arr, "r"); + assertToken(arr, "i"); + assertToken(arr, "t"); + const count = isXcrit ? parseNumber(arr, n) : 1; + crit = new Dice(); + for (let i = 0; i < count; i++) { + const max = finalResult.maxFace(); + crit.setFace(max, finalResult.get(max)); + finalResult = finalResult.deleteFace(max); + } + critNorm = crit.total(); + crit = op.call(crit, parseBinaryArgument(arg, arr, n)); + critNorm = crit && critNorm ? crit.total() / critNorm : 1; + } + let save; + let saveNorm = 1; + if (arr[0] === "s") { + assertToken(arr, "s"); + assertToken(arr, "a"); + assertToken(arr, "v"); + assertToken(arr, "e"); + save = new Dice(); + const min = finalResult.minFace(); + save.increment(min > 0 ? min : 1, finalResult.get(min)); + saveNorm = save.total(); + finalResult = finalResult.deleteFace(min); + save = op.call(save, parseBinaryArgument(arg, arr, n)); + saveNorm = save && saveNorm ? save.total() / saveNorm : 1; + } + let pc; + let pcNorm = 1; + if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { + assertToken(arr, "p"); + assertToken(arr, "c"); + pc = new Dice(); + const min = finalResult.minFace(); + pc.increment(min > 0 ? min : 1, finalResult.get(min)); + const missBefore = pc.total(); + finalResult = finalResult.deleteFace(min); + pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); + const missAfter = pc ? pc.total() : 0; + pcNorm = missBefore ? missAfter / missBefore : 1; + } + let miss; + let missNorm = 1; + if (arr[0] === "m") { + assertToken(arr, "m"); + assertToken(arr, "i"); + assertToken(arr, "s"); + assertToken(arr, "s"); + miss = new Dice(); + const min = finalResult.minFace(); + miss.increment(min > 0 ? min : 1, finalResult.get(min)); + missNorm = miss.total(); + finalResult = finalResult.deleteFace(min); + miss = op.call(miss, parseBinaryArgument(arg, arr, n)); + missNorm = miss && missNorm ? miss.total() / missNorm : 1; + } + let norm = finalResult.total(); + finalResult = op.call(finalResult, arg); + norm = norm ? finalResult.total() / norm : 1; + if (crit) { + const result2 = combineDiceWithNormalization( + crit, + critNorm, + "crit", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (save) { + const result2 = combineDiceWithNormalization( + save, + saveNorm, + "saveHalf", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (miss) { + const result2 = combineDiceWithNormalization( + miss, + missNorm, + "missDamage", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (pc) { + const result2 = combineDiceWithNormalization( + pc, + pcNorm, + "pc", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + op = parseOperation(arr); + } + return finalResult; +} +function parseArgument(s, n) { + let result = parseArgumentInternal(s, n); + while (true) { + const next = parseArgumentInternal(s, n); + if (next === void 0) break; + result = multiplyDiceByDice(result, next); + } + return result; +} +function multiplyDiceByDice(d1, d2) { + if (typeof d1 === "number") d1 = Dice.scalar(d1); + if (typeof d2 === "number") d2 = Dice.scalar(d2); + const result = new Dice(); + const faces = /* @__PURE__ */ new Map(); + let normalizationFactor = 1; + for (const key of d1.keys()) { + let face; + if (typeof key !== "number") { + continue; + } + if (d2.privateData.keep) { + const faceCount = d2.keys().length; + if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { + throw new DiceParseError( + `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` + ); + } + const repeat = Array(key).fill(d2); + face = opDice(repeat, d2.privateData.keep); + } else { + face = multiplyDice(key, d2); + } + normalizationFactor *= face.total(); + faces.set(key, face); + } + for (const [k, face] of faces) { + const count = d1.get(k); + result.combineInPlace( + face.normalize(count * normalizationFactor / face.total()) + ); + } + result.privateData.except = {}; + return result; +} +function multiplyDice(n, d2) { + if (n > MAX_DICE_COUNT) { + throw new DiceParseError( + `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` + ); + } + if (n === 0) return new Dice(0); + if (n === 1) return d2; + const half = Math.floor(n / 2); + let result = multiplyDice(half, d2); + result = result.add(result); + if (n % 2 === 1) { + result = result.add(d2); + } + return result; +} +function opDice(diceList, keepFn) { + return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); +} +function opDiceInternal(diceList, result, index, values, weight, combineFn) { + if (index === diceList.length) { + return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); + } + const currentDice = diceList[index]; + for (const face of currentDice.keys()) { + values.push(face); + result = opDiceInternal( + diceList, + result, + index + 1, + values, + weight * currentDice.get(face), + combineFn + ); + values.pop(); + } + return result; +} +function parseArgumentInternal(s, n) { + if (s.length === 0) return; + const c = s[0]; + switch (c) { + case "(": + s.shift(); + return assertToken(s, ")", parseExpression(s, n)); + case "h": + case "d": + return parseDice(s, n); + case "k": + assertToken(s, "k"); + return parseKeep(s, n); + case "n": + return parseNumber(s, n); + default: + if (isDigit(c)) return parseNumber(s, n); + return; + } +} +function parseBinaryArgument(arg, arr, n) { + if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { + assertToken(arr, "half"); + const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; + return diceArg.divideRoundDown(2); + } + const parsed = parseArgument(arr, n); + return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; +} +function assertToken(s, expected, ret) { + for (const ch of expected) { + const found = s.shift(); + if (found !== ch) { + throw new Error(`Expected character '${ch}', found '${found}'`); + } + } + return ret; +} +function parseDice(s, n) { + let rerollOne = false; + if (peek(s, "hd") && peekIsNumber(s, 2)) { + assertToken(s, "h"); + assertToken(s, "d"); + rerollOne = true; + } else if (peek(s, "d") && peekIsNumber(s, 1)) { + assertToken(s, "d"); + } else { + return; + } + const sides = parseNumber(s, n); + if (sides > MAX_DIE_SIDES) { + throw new DiceParseError( + `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` + ); + } + let result = new Dice(sides); + if (rerollOne) { + result = result.reroll(1); + } + return result; +} +function peek(arr, expected) { + if (expected.length > arr.length) return false; + for (let i = 0; i < expected.length; i++) { + if (arr[i] !== expected.charAt(i)) return false; + } + return true; +} +function peekIsNumber(arr, index) { + if (index >= arr.length) return false; + return isDigit(arr[index]) || arr[index] === "n"; +} +function parseNumber(s, n) { + let ret = ""; + while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { + const ch = s.shift(); + ret += ch === "n" ? n.toString() : ch; + } + if (ret.length === 0) { + throw new Error(`Expected number, found: '${s[0]}'`); + } + return parseInt(ret, 10); +} +function isDigit(c) { + return c >= "0" && c <= "9"; +} +function parseKeep(s, n) { + let keepLowest = false; + if (peek(s, "l")) { + assertToken(s, "l"); + keepLowest = true; + } else if (peek(s, "h")) { + assertToken(s, "h"); + keepLowest = false; + } else { + return; + } + const keepCount = parseNumber(s, n); + const result = parseArgumentInternal(s, n); + if (result instanceof Dice) { + result.privateData.keep = keepN(keepCount, keepLowest); + return result; + } + throw new Error("Expected Dice after keep modifier"); +} +function keepN(n, low) { + return (values) => { + const sorted = [...values].sort((a, b) => low ? a - b : b - a); + return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); + }; +} +function parseOperation(s) { + switch (s[0]) { + case ")": + return; + case "a": + assertToken(s, "ac"); + return Dice.prototype.ac; + case "d": + assertToken(s, "dc"); + return Dice.prototype.dc; + case "!": + assertToken(s, "!"); + const adv = Dice.prototype.advantage; + adv.unary = true; + return adv; + case ">": + assertToken(s, ">"); + return Dice.prototype.max; + case "<": + assertToken(s, "<"); + return Dice.prototype.min; + case "+": + assertToken(s, "+"); + return Dice.prototype.addNonZero; + case "~": + assertToken(s, "~"); + assertToken(s, "+"); + return Dice.prototype.add; + case "-": + assertToken(s, "-"); + return Dice.prototype.subtract; + case "&": + assertToken(s, "&"); + return Dice.prototype.combine; + case "r": + assertToken(s, "reroll"); + return Dice.prototype.reroll; + case "*": + assertToken(s, "*"); + if (peek(s, "*")) { + assertToken(s, "*"); + return Dice.prototype.multiply; + } + return Dice.prototype.conditionalApply; + case "/": + assertToken(s, "/"); + if (s[0] === "/") { + assertToken(s, "/"); + return Dice.prototype.divideRoundDown; + } + return Dice.prototype.divideRoundUp; + case "=": + assertToken(s, "="); + return Dice.prototype.eq; + } + return; +} + +// src/builder/prob.ts +function d20PmfFromCdf(cdfPow, eps = EPS) { + const out = /* @__PURE__ */ new Map(); + let prev = 0; + for (let k = 1; k <= 20; k++) { + const cur = cdfPow(k); + const pk = cur - prev; + if (pk > 0) { + out.set(k, pk); + } + prev = cur; + } + return PMF.fromMap(out, eps); +} + +// src/builder/d20.ts +var cacheKeyMap = { + "flat-flat": "d20", + "flat-reroll": "hd20", + "advantage-flat": "d20 > d20", + "advantage-reroll": "hd20 > hd20", + "disadvantage-flat": "d20 < d20", + "disadvantage-reroll": "hd20 < hd20", + "elven accuracy-flat": "d20 > d20 > d20", + "elven accuracy-reroll": "hd20 > hd20 > hd20" +}; +function d20RollPMF(rollType, rerollOne = false) { + rollType = rollType || "flat"; + const cacheKeyLookup = `${rollType}-${rerollOne ? "reroll" : "flat"}`; + const cacheKey = cacheKeyMap[cacheKeyLookup]; + if (!cacheKey) { + throw new Error(`Invalid roll type: ${rollType}`); + } + const cached = pmfCache.get(cacheKey); + if (cached) return cached; + const base = d20PMF(rerollOne); + if (!rollType || rollType === "flat") { + pmfCache.set(cacheKey, base); + return base; + } + const p = new Array(21).fill(0); + for (const [r, rec] of base) { + const pr = typeof rec === "number" ? rec : rec.p; + if (r >= 1 && r <= 20) p[r] = pr; + } + const F = new Array(21).fill(0); + for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k]; + const eps = 0; + let result = base; + if (rollType === "advantage") { + result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps); + } else if (rollType === "elven accuracy") { + result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps); + } else if (rollType === "disadvantage") { + result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps); + } + pmfCache.set(cacheKey, result); + return result; +} +function d20PMF(rerollOne) { + const cacheKey = `flat-${rerollOne ? "reroll" : "flat"}`; + const cached = pmfCache.get(cacheKey); + if (cached) return cached; + const m = /* @__PURE__ */ new Map(); + const base = 1 / 20; + const rerollShare = base * base; + if (!rerollOne) { + for (let r = 1; r <= 20; r++) { + m.set(r, base); + } + } else { + for (let r = 1; r <= 20; r++) { + m.set(r, (r === 1 ? 0 : base) + rerollShare); + } + } + const result = PMF.fromMap(m, EPS); + pmfCache.set(cacheKey, result); + return result; +} + +// src/builder/roll.ts +var defaultConfig = { + count: 1, + sides: 0, + modifier: 0, + reroll: 0, + explode: 0, + minimum: 0, + bestOf: 0, + keep: void 0, + rollType: "flat" +}; +var rollConfigsEqual = (a, b) => { + return a.count === b.count && a.sides === b.sides && a.modifier === b.modifier && a.reroll === b.reroll && a.explode === b.explode && a.minimum === b.minimum && a.bestOf === b.bestOf && a.keep === b.keep && a.rollType === b.rollType; +}; +var configComplexityScore = (config) => { + return (config.reroll > 0 ? 1 : 0) + (config.explode > 0 ? 1 : 0) + (config.minimum > 0 ? 1 : 0) + (config.bestOf > 0 ? 1 : 0) + (config.keep !== void 0 ? 1 : 0) + (config.rollType !== "flat" ? 1 : 0); +}; +var RollBuilder = class _RollBuilder { + constructor(countOrConfigs = 1) { + // --- Dice Shortcut Methods --- + this.d4 = () => this.d(4); + this.d6 = () => this.d(6); + this.d8 = () => this.d(8); + this.d10 = () => this.d(10); + this.d12 = () => this.d(12); + this.d20 = () => this.d(20); + this.d100 = () => this.d(100); + if (typeof countOrConfigs === "number") { + const count = countOrConfigs; + if (isNaN(count)) throw new Error("Invalid NaN value for count"); + this.subRollConfigs = [ + { ...defaultConfig, count, isSubtraction: count < 0 } + ]; + } else { + this.subRollConfigs = countOrConfigs.map((c) => ({ ...c })); + } + } + create(configs) { + return new _RollBuilder(configs); + } + get lastConfig() { + return this.subRollConfigs[this.subRollConfigs.length - 1]; + } + hasHiddenState() { + return false; + } + getSubRollConfigs() { + return this.subRollConfigs.map((c) => ({ ...c })); + } + // for testing + static fromConfig(config) { + return new _RollBuilder([{ ...defaultConfig, ...config }]); + } + static fromConfigs(configs) { + return new _RollBuilder( + configs.map((config) => ({ ...defaultConfig, ...config })) + ); + } + static fromArgs(...args) { + if (args.length === 1) { + const arg = args[0]; + if (typeof arg === "number") { + if (isNaN(arg)) throw new Error("Invalid NaN value for argument"); + return new _RollBuilder(0).plus(arg); + } + if (typeof arg === "string") { + return new ParsedRollBuilder(arg); + } + if (arg instanceof _RollBuilder) { + return arg; + } + } + if (args.length === 2 || args.length === 3) { + const [count, sidesOrDie, modifier] = args; + if (typeof count !== "number") { + throw new Error("First argument must be a number for multi-arg call"); + } + if (isNaN(count)) throw new Error("Invalid NaN value for count argument"); + if (sidesOrDie instanceof _RollBuilder) { + if (sidesOrDie.hasHiddenState()) { + throw new Error( + "Cannot use a roll with hidden state (like a pooled roll) as a die type." + ); + } + const subRollConfigs = sidesOrDie.getSubRollConfigs(); + if (subRollConfigs.length === 0) { + const result = new _RollBuilder(0); + return modifier !== void 0 ? result.plus(modifier) : result; + } + const absCount = Math.abs(count); + const newConfigs = subRollConfigs.map((config) => ({ + ...config, + count: config.count * absCount, + modifier: config.modifier * absCount + })); + let resultBuilder = new _RollBuilder(newConfigs); + if (count < 0) { + const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); + resultBuilder = new _RollBuilder(negatedConfigs); + } + return modifier !== void 0 ? resultBuilder.plus(modifier) : resultBuilder; + } else if (typeof sidesOrDie === "number" || sidesOrDie === void 0) { + if (typeof sidesOrDie === "number" && isNaN(sidesOrDie)) + throw new Error("Invalid NaN value for sides argument"); + let builder = new _RollBuilder(count); + if (sidesOrDie && sidesOrDie > 0) { + builder = builder.d(sidesOrDie); + } + return modifier !== void 0 ? builder.plus(modifier) : builder; + } + } + throw new Error(`Invalid arguments passed: ${args.join(", ")}`); + } + // --- Core Dice Methods --- + d(sides) { + if (sides !== void 0 && isNaN(sides)) + throw new Error("Invalid NaN value for sides"); + if (sides === void 0) return this; + if (this.lastConfig.sides && this.lastConfig.sides > 0) { + throw new Error("Cannot add a die after adding a die"); + } + if (sides === 0) return this; + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].sides = sides; + return this.create(newConfigs); + } + plus(modOrRoll, die) { + if (typeof modOrRoll === "number" && isNaN(modOrRoll)) + throw new Error("Invalid NaN value for modOrRoll"); + if (die instanceof _RollBuilder && typeof modOrRoll === "number") { + if (die.hasHiddenState()) { + throw new Error( + "Cannot use a roll with hidden state (like a pooled roll) as a die type." + ); + } + const count = modOrRoll; + const subRollConfigs = die.getSubRollConfigs(); + if (subRollConfigs.length === 0) return this; + const absCount = Math.abs(count); + const newConfigs = subRollConfigs.map((config) => ({ + ...config, + count: config.count * absCount, + modifier: config.modifier * absCount + })); + let rollToAdd = new _RollBuilder(newConfigs); + if (count < 0) { + const negatedConfigs = rollToAdd.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); + rollToAdd = new _RollBuilder(negatedConfigs); + } + return this.add(rollToAdd); + } + if (die !== void 0) { + throw new Error("Invalid arguments to plus()"); + } + if (modOrRoll === void 0) return this; + if (typeof modOrRoll === "number") { + if (modOrRoll === 0) return this; + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].modifier += modOrRoll; + return this.create(newConfigs); + } + return this.add(modOrRoll); + } + minus(modOrRoll, die) { + const isNumber = typeof modOrRoll === "number"; + const dieIsRoll = die instanceof _RollBuilder; + if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die); + if (die !== void 0) throw new Error("Invalid arguments to minus()"); + if (modOrRoll === void 0) return this; + return isNumber ? this.plus(-modOrRoll) : this.plus(-1, modOrRoll); + } + /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ + reroll(value) { + if (isNaN(value)) throw new Error("Invalid NaN value for reroll"); + if (value === this.lastConfig.reroll) return this; + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].reroll = value; + return this.create(newConfigs); + } + /** Set finite explode count for max-face explosions (Infinity allowed). */ + explode(count = Infinity) { + if (count !== void 0 && isNaN(count)) + throw new Error("Invalid NaN value for explode count"); + if (count === void 0) return this; + if (count === 0) return this; + if (count < 0) throw new Error("Explode count must be >= 0"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].explode = count; + return this.create(newConfigs); + } + /** Apply per-die minimum value (min > 0). */ + minimum(val) { + if (val !== void 0 && isNaN(val)) + throw new Error("Invalid NaN value for minimum"); + if (val === void 0) return this; + if (val === 0) return this; + if (val < 0) throw new Error("Minimum value must be >= 0"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].minimum = val + 1; + return this.create(newConfigs); + } + bestOf(count) { + if (count !== void 0 && isNaN(count)) + throw new Error("Invalid NaN value for bestOf count"); + if (count === void 0) return this; + if (count <= 0) throw new Error("Best of count must be > 0"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].bestOf = count; + return this.create(newConfigs); + } + keepHighest(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepHighest"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].keep = { total, count, mode: "highest" }; + return this.create(newConfigs); + } + keepLowest(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepLowest"); + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].keep = { total, count, mode: "lowest" }; + return this.create(newConfigs); + } + keepHighestAll(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepHighestAll"); + const currentAST = this.toAST(); + const trialPool = { + type: "sum", + count: total, + child: currentAST + }; + const keepNode = { + type: "keep", + mode: "highest", + count, + child: trialPool + }; + const currentExpr = this.toExpression(); + const expression = `${total}kh${count}(${currentExpr})`; + return new PooledRollBuilder(keepNode, expression); + } + keepLowestAll(total, count) { + if (isNaN(total) || isNaN(count)) + throw new Error("Invalid NaN value for keepLowestAll"); + const currentAST = this.toAST(); + const trialPool = { + type: "sum", + count: total, + child: currentAST + }; + const keepNode = { + type: "keep", + mode: "lowest", + count, + child: trialPool + }; + const currentExpr = this.toExpression(); + const expression = `${total}kl${count}(${currentExpr})`; + return new PooledRollBuilder(keepNode, expression); + } + withAdvantage() { + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].rollType = "advantage"; + return this.create(newConfigs); + } + withDisadvantage() { + const configs = this.getSubRollConfigs(); + configs[configs.length - 1].rollType = "disadvantage"; + return this.create(configs); + } + add(anotherRoll) { + if (anotherRoll === void 0) return this; + if (anotherRoll.hasHiddenState()) { + throw new Error( + "Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll)." + ); + } + const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; + return this.create(configs); + } + withBonus(anotherRoll) { + const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; + return this.create(configs); + } + addRoll(count = 1) { + if (isNaN(count)) throw new Error("Invalid NaN value for count"); + const configs = [ + ...this.subRollConfigs, + { + ...defaultConfig, + count, + isSubtraction: count < 0 + } + ]; + return this.create(configs); + } + scaleDice(scale) { + const scaleInt = Math.floor(scale); + if (scaleInt !== scale) throw new Error("Scale must be an integer"); + if (scaleInt <= 0) throw new Error("Scale must be > 0"); + const newConfigs = this.getSubRollConfigs().map((config) => { + if (!config.sides || config.sides <= 0) return config; + return { ...config, count: config.count * scaleInt }; + }); + return this.create(newConfigs); + } + doubleDice() { + return this.scaleDice(2); + } + alwaysHits() { + return new AlwaysHitBuilder(this); + } + alwaysCrits() { + return new AlwaysCritBuilder(this); + } + copy() { + return this.create(this.getSubRollConfigs()); + } + withElvenAccuracy() { + const newConfigs = this.getSubRollConfigs(); + newConfigs[newConfigs.length - 1].rollType = "elven accuracy"; + return this.create(newConfigs); + } + toExpression() { + const originalDiceConfigs = this.subRollConfigs.filter( + (config) => config.sides && config.sides > 0 + ); + const configGroups = /* @__PURE__ */ new Map(); + for (const config of originalDiceConfigs) { + const keyConfig = { ...config }; + delete keyConfig.count; + delete keyConfig.modifier; + const key = JSON.stringify(keyConfig); + const existingGroup = configGroups.get(key); + if (existingGroup) { + existingGroup.totalCount += config.count; + } else { + configGroups.set(key, { config, totalCount: config.count }); + } + } + const rootConfig = this.getRootDieConfig(); + const groupedConfigs = Array.from(configGroups.values()); + let rootD20Group; + if (rootConfig && rootConfig.sides === 20) { + const rootIndex = groupedConfigs.findIndex( + ({ config }) => rollConfigsEqual(config, rootConfig) && JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep) + ); + if (rootIndex !== -1) { + rootD20Group = groupedConfigs.splice(rootIndex, 1)[0]; + } + } + const sortedDiceConfigs = groupedConfigs.map(({ config, totalCount }) => ({ + ...config, + count: totalCount + })).sort((a, b) => { + const aHasPriority = a.reroll > 0 || a.minimum > 0; + const bHasPriority = b.reroll > 0 || b.minimum > 0; + if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1; + if (b.sides !== a.sides) return b.sides - a.sides; + return configComplexityScore(b) - configComplexityScore(a); + }); + const diceConfigs = rootD20Group ? [ + { ...rootD20Group.config, count: rootD20Group.totalCount }, + ...sortedDiceConfigs + ] : sortedDiceConfigs; + const totalModifier = this.subRollConfigs.reduce( + (sum, config) => sum + config.modifier, + 0 + ); + if (diceConfigs.length === 0) return totalModifier.toString(); + const rootDieConfig = this.getRootDieConfig(); + const newRootConfig = rootDieConfig ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig)) : void 0; + const diceExpressions = diceConfigs.map( + (config) => this.configToSingleExpressionWithoutModifier( + config, + config === newRootConfig + ) + ); + let result = ""; + for (let i = 0; i < diceExpressions.length; i++) { + const config = diceConfigs[i]; + const expression = diceExpressions[i]; + if (i === 0) { + result = (config.isSubtraction ? "-" : "") + expression; + if (config.sides === 20 && totalModifier !== 0) { + if (totalModifier > 0) result += ` + ${totalModifier}`; + else result += ` - ${Math.abs(totalModifier)}`; + } + } else { + const operator = config.isSubtraction ? " - " : " + "; + result += operator + expression; + } + } + if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) { + if (totalModifier > 0) result += ` + ${totalModifier}`; + else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`; + } + return result.replace(/\+ -/g, "-"); + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + get pmf() { + return this.toPMF(); + } + toQuery(eps = 0) { + return this.toPMF(eps).query(); + } + toAST() { + const configs = this.getSubRollConfigs(); + return astFromRollConfigs(configs) || { type: "constant", value: 0 }; + } + configToSingleExpressionWithoutModifier(config, isRootDie) { + if (!config.sides || config.sides <= 0) return ""; + let baseDie = `d${config.sides}`; + if (config.reroll > 0) { + if (config.minimum > 0 && config.explode > 0) ; else if (config.minimum > 0) { + for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`; + } else { + for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`; + } + } + if (config.minimum > 0) { + if (config.reroll > 0 && !config.explode) { + baseDie = `${config.minimum}>(${baseDie})`; + } else { + baseDie = `${config.minimum}>${baseDie}`; + } + if (config.reroll > 0 && config.explode > 0) { + for (let i = 1; i <= config.reroll; i++) { + baseDie += ` reroll ${i}`; + } + } + } + if (baseDie === "d20 reroll 1" && config.minimum <= 1) baseDie = "hd20"; + let mainExpression = ""; + switch (config.rollType) { + case "advantage": + mainExpression = `${baseDie} > ${baseDie}`; + break; + case "disadvantage": + mainExpression = `${baseDie} < ${baseDie}`; + break; + case "elven accuracy": + mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`; + break; + case "flat": + if (config.keep) { + const mode = config.keep.mode === "highest" ? "kh" : "kl"; + const baseDieExpression = this.configToSingleExpressionWithoutModifier( + { + ...config, + count: config.count, + modifier: 0, + rollType: "flat", + keep: void 0 + }, + false + ); + mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`; + } else { + const isComplex = baseDie.length > `d${config.sides}`.length; + const isHalflingShorthand = baseDie === "hd20"; + const isD20Shorthand = baseDie === "d20" && isRootDie; + const hasMinimum = config.minimum > 0; + const hasReroll = config.reroll > 0; + const effectiveCount = config.isSubtraction ? Math.abs(config.count) : config.count < 0 ? 1 : Math.abs(config.count); + if (effectiveCount > 1) { + const shouldAddParentheses = isComplex; + mainExpression = shouldAddParentheses ? `${effectiveCount}(${baseDie})` : `${effectiveCount}${baseDie}`; + } else if (effectiveCount === 1) { + const needsParens = hasReroll && hasMinimum; + if (config.isSubtraction) { + mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; + } else if (isComplex || isHalflingShorthand || isD20Shorthand || config.count < 0) { + mainExpression = needsParens ? `1(${baseDie})` : baseDie; + } else { + mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; + } + } else { + mainExpression = baseDie; + } + } + if (config.bestOf && config.count && config.bestOf < config.count) { + mainExpression += `kh${config.bestOf}`; + } + break; + } + return mainExpression; + } + getRootDieConfig() { + const configs = this.subRollConfigs; + return configs.find((config) => config.sides > 0) || configs[0]; + } + getAllDieConfigs() { + return this.getSubRollConfigs(); + } + getBonusDiceConfigs() { + const allConfigs = this.subRollConfigs; + const rootConfig = allConfigs.find((config) => config.sides > 0) || allConfigs[0]; + if (!rootConfig) return []; + return allConfigs.filter((config) => config.sides > 0).filter((config) => config !== rootConfig); + } + getBonusDicePMFs(check, eps = 0) { + return check.getBonusDiceConfigs().map( + (config) => pmfFromRollBuilder(_RollBuilder.fromConfigs([config]), eps) + ); + } + get modifier() { + return this.subRollConfigs.reduce( + (sum, config) => sum + config.modifier, + 0 + ); + } + get rollType() { + const rootConfig = this.getRootDieConfig(); + return rootConfig?.rollType || "flat"; + } + get baseReroll() { + const rootConfig = this.getRootDieConfig(); + return rootConfig?.reroll || 0; + } + half() { + return new HalfRollBuilder(this); + } + /** + * Scale this roll's result by `numerator / denominator`, rounding each outcome. + * A general, composable form of {@link half} — used to model damage-type resistance + * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). + * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. + */ + scaleResult(numerator, denominator = 1, rounding = "floor") { + return new ScaleRollBuilder(this, numerator, denominator, rounding); + } + // Create a "max of N rolls" version of this roll for crit damage with keep operations + maxOf(count) { + return new MaxOfRollBuilder(this, count); + } + // These methods are implemented via prototype augmentation in ac.ts and dc.ts + // They are declared here to provide proper TypeScript types + ac(_targetAC) { + throw new Error("ac() should be implemented via prototype augmentation"); + } + dc(_saveDC) { + throw new Error("dc() should be implemented via prototype augmentation"); + } +}; +var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder { + constructor(innerRoll) { + super(0); + this.innerRoll = innerRoll; + } + hasHiddenState() { + return this.innerRoll.hasHiddenState(); + } + // No need to override create if we don't expose RollBuilder methods that use it, + // but HalfRollBuilder extends RollBuilder so it does. + // However, HalfRollBuilder seems to just wrap another roll. + // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder? + // No, RollBuilder.plus returns RollBuilder. + // The inheritance here is a bit tricky. + // Existing code for HalfRollBuilder doesn't seem to implement plus/etc. + // So .plus() on a HalfRollBuilder would return a RollBuilder (base class). + // Which is fine. + // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that. + get lastConfig() { + return this.innerRoll.lastConfig; + } + getSubRollConfigs() { + return this.innerRoll.getSubRollConfigs(); + } + toExpression() { + const innerExpression = this.innerRoll.toExpression(); + return `(${innerExpression}) // 2`; + } + toAST() { + return { + type: "half", + child: this.innerRoll.toAST() + }; + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _HalfRollBuilder(this.innerRoll.copy()); + } +}; +var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder { + constructor(innerRoll, numerator, denominator = 1, rounding = "floor") { + super(0); + this.innerRoll = innerRoll; + this.numerator = numerator; + this.denominator = denominator; + this.rounding = rounding; + } + hasHiddenState() { + return this.innerRoll.hasHiddenState(); + } + get lastConfig() { + return this.innerRoll.lastConfig; + } + getSubRollConfigs() { + return this.innerRoll.getSubRollConfigs(); + } + toExpression() { + const inner = this.innerRoll.toExpression(); + if (this.denominator === 1) return `${this.numerator} * (${inner})`; + if (this.numerator === 1) return `(${inner}) // ${this.denominator}`; + return `(${inner}) * ${this.numerator} // ${this.denominator}`; + } + toAST() { + return { + type: "scale", + numerator: this.numerator, + denominator: this.denominator, + rounding: this.rounding, + child: this.innerRoll.toAST() + }; + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _ScaleRollBuilder( + this.innerRoll.copy(), + this.numerator, + this.denominator, + this.rounding + ); + } +}; +var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder { + constructor(innerRoll, count, diceCount, diceSides) { + super(0); + this.innerRoll = innerRoll; + this.count = count; + this.diceCount = diceCount; + this.diceSides = diceSides; + } + hasHiddenState() { + return this.innerRoll.hasHiddenState(); + } + get lastConfig() { + return this.innerRoll.lastConfig; + } + getSubRollConfigs() { + return this.innerRoll.getSubRollConfigs(); + } + toExpression() { + if (this.diceCount && this.diceSides) { + return `max${this.count}(${this.diceCount}d${this.diceSides})`; + } + return `max${this.count}(?d?)`; + } + toAST() { + if (this.diceCount && this.diceSides) { + const sumChild = { + type: "sum", + count: this.diceCount, + child: { type: "die", sides: this.diceSides } + }; + return { + type: "maxOf", + count: this.count, + child: sumChild + }; + } + try { + const configs = this.innerRoll.getSubRollConfigs(); + if (configs.length === 1 && configs[0].sides) { + const config = configs[0]; + const sumChild = { + type: "sum", + count: config.count, + child: { type: "die", sides: config.sides } + }; + return { + type: "maxOf", + count: this.count, + child: sumChild + }; + } + } catch { + } + throw new Error( + `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration` + ); + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count); + } +}; +var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder { + constructor(baseRoll, attackConfig) { + if (baseRoll.hasHiddenState()) { + throw new Error( + "Cannot create AlwaysHitBuilder from a roll with hidden state." + ); + } + super(baseRoll.getSubRollConfigs()); + if (attackConfig) { + this.attackConfig = { ...attackConfig }; + } else { + this.attackConfig = { critThreshold: 20 }; + } + } + create(configs) { + return new RollBuilder(configs); + } + onHit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new AttackBuilder(this, damageRoll); + } + get critThreshold() { + return this.attackConfig.critThreshold; + } + // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? + critOn(critThreshold) { + const newConfig = { critThreshold }; + return new _AlwaysHitBuilder(this, newConfig); + } + alwaysCrits() { + return new AlwaysCritBuilder(this, void 0, true); + } + // Legacy expressions + toExpression() { + const configs = this.getSubRollConfigs(); + return new RollBuilder(configs).toExpression(); + } + toPMF() { + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + return d20RollPMF(rollType, rerollOne); + } + copy() { + const baseCopy = new RollBuilder(this.getSubRollConfigs()); + const critThreshold = this.critThreshold; + const newConfig = { critThreshold }; + return new _AlwaysHitBuilder(baseCopy, newConfig); + } +}; +var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder { + constructor(baseRoll, attackConfig, fromAlwaysHit = false) { + if (baseRoll.hasHiddenState()) { + throw new Error( + "Cannot create AlwaysCritBuilder from a roll with hidden state." + ); + } + super(baseRoll.getSubRollConfigs()); + if (attackConfig) { + this.attackConfig = { ...attackConfig }; + } else { + this.attackConfig = { critThreshold: 20 }; + } + this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder; + } + create(configs) { + return new RollBuilder(configs); + } + onHit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new AttackBuilder(this, damageRoll); + } + get critThreshold() { + return this.attackConfig.critThreshold; + } + critOn(critThreshold) { + const newConfig = { critThreshold, ac: this.attackConfig.ac }; + return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit); + } + // Legacy expressions + toExpression() { + const configs = this.getSubRollConfigs(); + return new RollBuilder(configs).toExpression(); + } + toPMF() { + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + return d20RollPMF(rollType, rerollOne); + } + copy() { + const baseCopy = new RollBuilder(this.getSubRollConfigs()); + const critThreshold = this.critThreshold; + const newConfig = { critThreshold, ac: this.attackConfig.ac }; + return new _AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit); + } +}; +var ParsedRollBuilder = class _ParsedRollBuilder extends RollBuilder { + constructor(expression) { + super([]); + this.originalExpression = expression; + this.cachedPMF = parse(expression, 0); + } + hasHiddenState() { + return true; + } + create(configs) { + return new RollBuilder(configs); + } + toPMF(_eps = 0) { + return this.cachedPMF; + } + toExpression() { + return this.originalExpression; + } + toAST() { + throw new Error( + "ParsedRollBuilder does not support AST conversion. Use the builder API instead." + ); + } + copy() { + return new _ParsedRollBuilder(this.originalExpression); + } + doubleDice() { + throw new Error( + "ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead." + ); + } +}; +var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder { + constructor(baseAST, baseExpression, configs = []) { + super(configs.length > 0 ? configs : 0); + this.baseAST = baseAST; + this.baseExpression = baseExpression; + } + create(configs) { + return new _PooledRollBuilder(this.baseAST, this.baseExpression, configs); + } + hasHiddenState() { + return true; + } + d(_sides) { + throw new Error("Cannot add dice to a pooled roll. The pool is finalized."); + } + reroll(_value) { + throw new Error("Cannot set reroll on a pooled roll."); + } + explode(_count = Infinity) { + throw new Error("Cannot set explode on a pooled roll."); + } + minimum(_val) { + throw new Error("Cannot set minimum on a pooled roll."); + } + bestOf(_count) { + throw new Error("Cannot set bestOf on a pooled roll."); + } + keepHighest(_total, _count) { + throw new Error( + "Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling." + ); + } + keepLowest(_total, _count) { + throw new Error( + "Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling." + ); + } + withAdvantage() { + throw new Error("Cannot set advantage on a pooled roll."); + } + withDisadvantage() { + throw new Error("Cannot set disadvantage on a pooled roll."); + } + withElvenAccuracy() { + throw new Error("Cannot set elven accuracy on a pooled roll."); + } + toAST() { + const configsAST = super.toAST(); + const isZero = configsAST.type === "constant" && configsAST.value === 0; + if (isZero) { + return this.baseAST; + } + const children = [ + { node: this.baseAST, sign: 1 }, + { node: configsAST, sign: 1 } + ]; + return { type: "add", children }; + } + toExpression() { + const configsExpression = super.toExpression(); + if (configsExpression === "0") { + return this.baseExpression; + } + if (configsExpression.startsWith("-")) { + return `${this.baseExpression} - ${configsExpression.substring(1)}`; + } + return `${this.baseExpression} + ${configsExpression}`; + } + copy() { + return new _PooledRollBuilder( + this.baseAST, + this.baseExpression, + this.getSubRollConfigs() + ); + } + scaleDice(scale) { + const scaleInt = Math.floor(scale); + if (scaleInt !== scale) throw new Error("Scale must be an integer"); + if (scaleInt <= 0) throw new Error("Scale must be > 0"); + const newBaseAST = { + type: "sum", + count: scaleInt, + child: this.baseAST + }; + const newBaseExpr = scaleInt === 1 ? this.baseExpression : `${scaleInt}(${this.baseExpression})`; + return new _PooledRollBuilder( + newBaseAST, + newBaseExpr, + this.getSubRollConfigs() + ); + } + times(count) { + if (isNaN(count)) throw new Error("Invalid NaN value for times"); + if (Math.floor(count) !== count) + throw new Error("times() requires an integer"); + if (count < 0) throw new Error("times() requires a non-negative integer"); + const currentAST = this.toAST(); + const currentExpr = this.toExpression(); + const sumNode = { + type: "sum", + count, + child: currentAST + }; + const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`; + return new _PooledRollBuilder(sumNode, newExpr); + } +}; +var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder { + constructor(parts) { + super(0); + this.parts = parts; + } + hasHiddenState() { + return true; + } + getSubRollConfigs() { + return []; + } + toAST() { + return { + type: "add", + children: this.parts.map((p) => ({ + node: p.toAST(), + sign: 1 + })) + }; + } + toExpression() { + const exprs = this.parts.map((p) => p.toExpression()).filter((e) => e && e !== "0"); + if (exprs.length === 0) return "0"; + let result = exprs[0]; + for (let i = 1; i < exprs.length; i++) { + const e = exprs[i]; + result += e.startsWith("-") ? ` - ${e.substring(1)}` : ` + ${e}`; + } + return result.replace(/\+ -/g, "-"); + } + toPMF(eps = 0) { + return pmfFromRollBuilder(this, eps); + } + copy() { + return new _CompositeSumRollBuilder(this.parts.map((p) => p.copy())); + } +}; +function sumRolls(parts) { + const meaningful = parts.filter((p) => p !== void 0); + if (meaningful.length === 0) return new RollBuilder(0); + if (meaningful.length === 1) return meaningful[0]; + return new CompositeSumRollBuilder(meaningful); +} + +// src/builder/factory.ts +var rollFn = (count, sidesOrDie, modifier) => { + if (sidesOrDie instanceof RollBuilder) { + if (sidesOrDie.hasHiddenState()) { + throw new Error( + "Cannot use a roll with hidden state (like a pooled roll) as a die type." + ); + } + const subRollConfigs = sidesOrDie.getSubRollConfigs(); + if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier); + const absCount = Math.abs(count); + const newConfigs = subRollConfigs.map((config) => ({ + ...config, + count: config.count * absCount, + modifier: config.modifier * absCount + })); + let resultBuilder = new RollBuilder(newConfigs); + if (count < 0) { + const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); + resultBuilder = new RollBuilder(negatedConfigs); + } + return resultBuilder.plus(modifier); + } else { + let builder = new RollBuilder(count); + if (sidesOrDie && sidesOrDie > 0) { + builder = builder.d(sidesOrDie); + } + return builder.plus(modifier); + } +}; +rollFn.d = (sides) => { + if (typeof sides === "string") { + return RollBuilder.fromArgs(sides); + } + return new RollBuilder(1).d(sides); +}; +rollFn.hd20 = () => new RollBuilder(1).d20().reroll(1); +rollFn.d4 = () => new RollBuilder(1).d4(); +rollFn.d6 = () => new RollBuilder(1).d6(); +rollFn.d8 = () => new RollBuilder(1).d8(); +rollFn.d10 = () => new RollBuilder(1).d10(); +rollFn.d12 = () => new RollBuilder(1).d12(); +rollFn.d20 = () => new RollBuilder(1).d20(); +rollFn.d100 = () => new RollBuilder(1).d100(); +rollFn.flat = (n) => new RollBuilder(0).plus(n); +function d(sides) { + if (typeof sides === "string") { + return RollBuilder.fromArgs(sides); + } + return new RollBuilder(1).d(sides); +} +var d4 = new RollBuilder(1).d4(); +var d6 = new RollBuilder(1).d6(); +var d8 = new RollBuilder(1).d8(); +var d10 = new RollBuilder(1).d10(); +var d12 = new RollBuilder(1).d12(); +var d20 = new RollBuilder(1).d20(); +var hd20 = new RollBuilder(1).d20().reroll(1); +var d100 = new RollBuilder(1).d100(); +var flat = (n) => new RollBuilder(0).plus(n); +var roll = rollFn; +var builderPMFCache = new LRUCache(1e3); + +// src/builder/ast.ts +var defaultEps = 0; +var singleDiePMFCache = new LRUCache(1e3); +function astFromRollConfigs(configs) { + if (!configs || configs.length === 0) return void 0; + const children = []; + let constantSum = 0; + for (const cfg of configs) { + const sign = cfg.isSubtraction || cfg.count < 0 ? -1 : 1; + const count = Math.abs(cfg.count || 0); + constantSum += cfg.modifier || 0; + if ((cfg.sides || 0) <= 0) continue; + const die = { + type: "die", + sides: cfg.sides, + reroll: cfg.reroll > 0 ? cfg.reroll : void 0, + minimum: cfg.minimum > 0 ? cfg.minimum : void 0, + explode: cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0 ? cfg.explode : void 0 + }; + let node = die; + let appliedRollType = false; + if (cfg.rollType && cfg.rollType !== "flat") { + if (cfg.sides === 20) { + node = { + type: "d20Roll", + rollType: cfg.rollType, + child: node + }; + } else { + const n = cfg.rollType === "elven accuracy" ? 3 : 2; + const mode = cfg.rollType === "disadvantage" ? "lowest" : "highest"; + const base = { type: "sum", count: n, child: node }; + node = { type: "keep", mode, count: 1, child: base }; + } + appliedRollType = true; + } + if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) { + const baseCount = Math.max(1, Math.floor(Math.abs(count || 1))); + const trials = Math.max(1, Math.floor(cfg.keep.total)); + const k = Math.max(0, Math.floor(cfg.keep.count)); + if (k === 1 && cfg.keep.mode === "highest") { + const perTrial = { + type: "sum", + count: baseCount, + child: node + }; + if (trials === 1) { + node = perTrial; + } else { + node = { + type: "maxOf", + count: trials, + child: perTrial + }; + } + } else if (trials === baseCount) { + const base = { type: "sum", count: trials, child: node }; + node = { + type: "keep", + mode: cfg.keep.mode, + count: k, + child: base + }; + } else { + const perTrial = { + type: "sum", + count: baseCount, + child: node + }; + if (trials === 1) { + node = perTrial; + } else { + const trialPool = { + type: "sum", + count: trials, + child: perTrial + }; + node = { + type: "keep", + mode: cfg.keep.mode, + count: k, + child: trialPool + }; + } + } + } else { + const c = appliedRollType ? 1 : Math.max(1, count || 1); + node = { type: "sum", count: c, child: node }; + } + children.push({ node, sign }); + } + if (children.length === 0) { + return { type: "constant", value: constantSum }; + } + const add = { type: "add", children }; + if (constantSum !== 0) + add.children.push({ + node: { type: "constant", value: constantSum }, + sign: 1 + }); + return add; +} +function resolve(node, eps = defaultEps) { + const signature = getASTSignature(node); + const cacheKey = `${signature}_${eps}`; + const cached = builderPMFCache.get(cacheKey); + if (cached) return cached; + const result = (() => { + switch (node.type) { + case "constant": + return PMF.delta(node.value, eps); + case "die": { + return resolveSingleDie(node, eps); + } + case "sum": { + const base = resolve(node.child, eps); + const n = Math.max(0, Math.floor(node.count)); + if (n === 0) return PMF.delta(0, eps); + if (n === 1) return base; + return base.power(n, eps); + } + case "add": { + let shift = 0; + const parts = []; + for (const c of node.children) { + if (c.node.type === "constant") { + shift += c.sign * c.node.value; + } else { + const p = resolve(c.node, eps); + parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v)); + } + } + if (parts.length === 0) return PMF.delta(shift, eps); + let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps); + if (shift !== 0) res = res.mapDamage((v) => v + shift); + return res; + } + case "keep": { + const totalTrials = getTotalCount(node); + const keepCount = Math.max(0, Math.min(node.count, totalTrials)); + if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps); + const perTrialNode = node.child.child; + const perTrialPMF = resolve(perTrialNode, eps); + return keepSumPMF( + perTrialPMF, + totalTrials, + keepCount, + node.mode === "highest", + eps + ); + } + case "d20Roll": { + const childDie = findDie(node.child); + const rerollOne = !!childDie && (childDie.reroll || 0) >= 1; + return d20RollPMF(node.rollType, rerollOne); + } + case "half": { + const childPMF = resolve(node.child, eps); + return childPMF.scaleDamage(0.5, "floor"); + } + case "maxOf": { + const childPMF = resolve(node.child, eps); + const count = Math.max(1, Math.floor(node.count)); + if (count === 1) return childPMF; + return computeMaxOfPMF(childPMF, count, eps); + } + case "scale": { + const childPMF = resolve(node.child, eps); + const denom = node.denominator === 0 ? 1 : node.denominator; + return childPMF.scaleDamage(node.numerator / denom, node.rounding); + } + } + })(); + builderPMFCache.set(cacheKey, result); + return result; +} +function pmfFromRollBuilder(rb, eps = defaultEps) { + const ast = rb.toAST(); + return resolve(ast, eps); +} +function resolveSingleDie(die, eps = defaultEps) { + const signature = getASTSignature(die); + const cacheKey = `${signature}_${eps}`; + const cached = singleDiePMFCache.get(cacheKey); + if (cached) return cached; + const s = Math.max(0, Math.floor(die.sides)); + if (s <= 0) return PMF.delta(0, eps); + let probs = /* @__PURE__ */ new Map(); + for (let v = 1; v <= s; v++) probs.set(v, 1 / s); + const r = Math.max(0, Math.floor(die.reroll || 0)); + if (r > 0) { + const k = Math.min(r, s); + const rerollMass = k / s; + const uniformReroll = rerollMass / s; + const next = /* @__PURE__ */ new Map(); + for (let v = 1; v <= s; v++) { + const keep = v <= k ? 0 : 1 / s; + next.set(v, keep + uniformReroll); + } + probs = next; + } + let pmf = PMF.fromMap(new Map(probs), eps); + const minV = Math.max(0, Math.floor(die.minimum || 0)); + if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV)); + const explode = die.explode; + if (explode && Number.isFinite(explode) && explode > 0) { + const times = Math.floor(explode); + const maxFace = s; + const nonMax = /* @__PURE__ */ new Map(); + const pMax = pmf.pAt(maxFace); + for (const v of pmf.support()) { + if (v !== maxFace) nonMax.set(v, pmf.pAt(v)); + } + let nonMaxPMF = PMF.fromMap(nonMax, eps); + if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) { + nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax); + } + let tail = PMF.delta(0, eps); + const addOnce = pmf; + for (let t = 1; t <= times; t++) { + tail = tail.convolve(addOnce, eps); + } + const exploded = PMF.branch( + tail.mapDamage((v) => v + maxFace), + nonMaxPMF, + pMax + ); + pmf = exploded; + } + singleDiePMFCache.set(cacheKey, pmf); + return pmf; +} +function findDie(node) { + switch (node.type) { + case "die": + return node; + case "constant": + return void 0; + case "sum": + case "d20Roll": + case "half": + case "maxOf": + case "scale": + return findDie(node.child); + case "keep": + return findDie(node.child.child); + case "add": + for (const c of node.children) { + const d2 = findDie(c.node); + if (d2) return d2; + } + return void 0; + } +} +function getTotalCount(node) { + let cur = node.child; + while (cur.type === "keep") cur = cur.child; + return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0; +} +function computeMaxOfPMF(pmf, count, eps = defaultEps) { + if (count <= 1) return pmf; + const support = pmf.support(); + const out = /* @__PURE__ */ new Map(); + if (count <= 6 && support.length <= 20) { + let dfs2 = function(rollsLeft, currentMax, probability) { + if (rollsLeft === 0) { + out.set(currentMax, (out.get(currentMax) || 0) + probability); + return; + } + for (const value of support) { + const p = pmf.pAt(value); + if (p > 0) { + const newMax = Math.max(currentMax, value); + dfs2(rollsLeft - 1, newMax, probability * p); + } + } + }; + dfs2(count, -Infinity, 1); + } else { + const sortedSupport = [...support].sort((a, b) => a - b); + let runningCdf = 0; + for (const value of sortedSupport) { + const prevCdf = runningCdf; + runningCdf += pmf.pAt(value); + const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count); + if (probMax > eps) { + out.set(value, probMax); + } + } + } + return PMF.fromMap(out, eps); +} +function keepSumPMF(single, total, keep, highest, eps = defaultEps) { + if (keep >= total) return single.power(total, eps); + if (keep <= 0) return PMF.delta(0, eps); + const sortedSupport = [...single.support()].sort((a, b) => a - b); + const pmfSig = sortedSupport.map((val) => `${val}:${single.pAt(val).toPrecision(6)}`).join(","); + const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`; + const cached = builderPMFCache.get(cacheKey); + if (cached) return cached; + if (keep === 1) { + if (highest) { + return computeMaxOfPMF(single, total, eps); + } else { + const neg = single.mapDamage((v) => -v); + const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v); + builderPMFCache.set(cacheKey, minPMF); + return minPMF; + } + } + let state = /* @__PURE__ */ new Map(); + const stride = total + 1; + const keyOf = (used, r) => used * stride + r; + state.set(keyOf(0, total), /* @__PURE__ */ new Map([[0, 1]])); + const valuesDesc = highest ? [...sortedSupport].sort((a, b) => b - a) : [...sortedSupport].sort((a, b) => a - b); + const binomPMF = (r, p) => { + if (r <= 0) return [1]; + if (p <= eps) { + const arr2 = new Array(r + 1).fill(0); + arr2[0] = 1; + return arr2; + } + if (1 - p <= eps) { + const arr2 = new Array(r + 1).fill(0); + arr2[r] = 1; + return arr2; + } + const q = 1 - p; + const arr = new Array(r + 1).fill(0); + arr[0] = Math.pow(q, r); + const ratio = p / q; + for (let x = 1; x <= r; x++) + arr[x] = arr[x - 1] * (r - x + 1) / x * ratio; + let s = 0; + for (let x = 0; x <= r; x++) s += arr[x]; + if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s; + return arr; + }; + const pruneMap = (m, threshold) => { + if (threshold <= 0) return m; + const out = /* @__PURE__ */ new Map(); + for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr); + return out.size === m.size ? m : out; + }; + const pruneState = (st, threshold) => { + if (threshold <= 0) return st; + const out = /* @__PURE__ */ new Map(); + for (const [k, m] of st) { + const mm = pruneMap(m, threshold); + if (mm.size > 0) out.set(k, mm); + } + return out; + }; + let processedMass = 0; + for (const v of valuesDesc) { + const p = single.pAt(v); + if (p <= 0) continue; + const q = Math.max(eps, 1 - processedMass); + const pCond = Math.min(1, p / q); + const next = /* @__PURE__ */ new Map(); + for (const [k, m] of state) { + const used = Math.floor(k / stride); + const r = k - used * stride; + if (r === 0) { + const destKey = keyOf(used, 0); + const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); + for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr); + next.set(destKey, dest); + continue; + } + const bin = binomPMF(r, pCond); + const remainingCapacity = keep - used; + for (let x = 0; x <= r; x++) { + const px = bin[x]; + if (px <= eps) continue; + const t = Math.min(x, remainingCapacity); + const used2 = used + t; + const r2 = r - x; + const add = t * v; + const destKey = keyOf(used2, r2); + const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); + for (const [sum, pr] of m) { + const s2 = sum + add; + const prob = pr * px; + const cur = dest.get(s2) || 0; + const nv = cur + prob; + if (nv >= eps) dest.set(s2, nv); + } + if (dest.size > 0) next.set(destKey, dest); + } + } + state = pruneState(next, eps * 1e-6); + processedMass += p; + } + const finalKey = keyOf(keep, 0); + const dist = state.get(finalKey) ?? /* @__PURE__ */ new Map(); + if (dist.size === 0) { + return PMF.emptyMass(); + } + const result = PMF.fromMap(dist, eps); + builderPMFCache.set(cacheKey, result); + return result; +} +function getASTSignature(node) { + switch (node.type) { + case "constant": + return `c:${node.value}`; + case "die": { + const parts = []; + parts.push(`s:${node.sides}`); + if (node.reroll) parts.push(`r:${node.reroll}`); + if (node.minimum) parts.push(`m:${node.minimum}`); + if (node.explode) parts.push(`e:${node.explode}`); + return `d{${parts.join(",")}}`; + } + case "sum": + return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`; + case "d20Roll": + return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`; + case "keep": + return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature( + node.child + )}}`; + case "half": + return `half{ch:${getASTSignature(node.child)}}`; + case "maxOf": + return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`; + case "scale": + return `scale{n:${node.numerator},d:${node.denominator},r:${node.rounding},ch:${getASTSignature(node.child)}}`; + case "add": { + let constantValue = 0; + const otherChildrenSigs = []; + for (const c of node.children) { + if (c.node.type === "constant") { + constantValue += c.sign * c.node.value; + } else { + otherChildrenSigs.push( + `${c.sign === -1 ? "-" : "+"}${getASTSignature(c.node)}` + ); + } + } + if (constantValue !== 0) { + otherChildrenSigs.push( + constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}` + ); + } + otherChildrenSigs.sort(); + return `add[${otherChildrenSigs.join("")}]`; + } + } +} + +// src/builder/attack.ts +var AttackBuilder = class _AttackBuilder { + constructor(check, hitEffect, critEffect, missEffect) { + this.check = check; + this.hitEffect = hitEffect; + this.critEffect = critEffect; + this.missEffect = missEffect; + } + onCrit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new _AttackBuilder( + this.check, + this.hitEffect, + damageRoll, + this.missEffect + ); + } + onMiss(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new _AttackBuilder( + this.check, + this.hitEffect, + this.critEffect, + damageRoll + ); + } + noCrit() { + return new _AttackBuilder(this.check, this.hitEffect, null, this.missEffect); + } + // Legacy expressions + toExpression() { + const checkPart = this.check.toExpression(); + let effectPart = ""; + if (this.hitEffect) { + effectPart = `(${this.hitEffect.toExpression()})`; + if (this.critEffect !== null) { + let crit; + if (this.critEffect) { + crit = this.critEffect; + } else { + if (this.hitEffect instanceof ParsedRollBuilder) { + crit = RollBuilder.fromArgs(0); + } else { + crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0); + } + } + const critThreshold = this.check.critThreshold; + if (critThreshold < 1 || critThreshold > 20) { + throw new Error( + `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.` + ); + } + const critExpression = crit.toExpression(); + if (critExpression !== "0") { + if (critThreshold === 20) { + effectPart += ` crit (${critExpression})`; + } else { + const xcritNumber = 21 - critThreshold; + effectPart += ` xcrit${xcritNumber} (${critExpression})`; + } + } + } + if (this.missEffect) { + effectPart += ` miss (${this.missEffect.toExpression()})`; + } + } + return `${checkPart} * ${effectPart}`; + } + resolveProbabilities(check, eps = 0) { + const rollType = check.rollType; + const rerollOne = check.baseReroll > 0; + const critThreshold = check.critThreshold; + const d202 = d20RollPMF(rollType, rerollOne); + if (check instanceof AlwaysCritBuilder) { + if (check.fromAlwaysHit) { + return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 }; + } + const ac2 = check.attackConfig.ac ?? 0; + const staticMod2 = this.check.modifier; + const bonusDicePMFs2 = this.check.getBonusDicePMFs(this.check, eps); + const bonusPMF2 = bonusDicePMFs2.length ? PMF.convolveMany(bonusDicePMFs2, eps) : PMF.delta(0, eps); + let pcrit2 = 0; + let pmiss2 = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + if (r === 1) { + pmiss2 += pr; + continue; + } + const need = ac2 - staticMod2 - r; + const pBonusHit = bonusPMF2.tailProbGE(need); + pcrit2 += pr * pBonusHit; + pmiss2 += pr * (1 - pBonusHit); + } + return { pSuccess: pcrit2, pHit: 0, pCrit: pcrit2, pMiss: pmiss2 }; + } + if (check instanceof AlwaysHitBuilder) { + let pCrit = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + if (r >= critThreshold) pCrit += pr; + } + const pHit = 1 - pCrit; + const pMiss = 0; + return { pSuccess: 1, pHit, pCrit, pMiss }; + } + const ac = check.attackConfig.ac; + const staticMod = this.check.modifier; + const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps); + const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); + let pcrit = 0; + let phit = 0; + let pmiss = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + if (r === 1) { + pmiss += pr; + continue; + } + if (r >= critThreshold) { + pcrit += pr; + continue; + } + const need = ac - staticMod - r; + const pBonusHit = bonusPMF.tailProbGE(need); + phit += pr * pBonusHit; + pmiss += pr * (1 - pBonusHit); + } + const psuccess = phit + pcrit; + return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss }; + } + resolve(eps = EPS) { + const { + pHit, + pCrit, + pMiss: pmiss + } = this.resolveProbabilities(this.check, eps); + const hitPMF = this.hitEffect ? this.hitEffect instanceof ParsedRollBuilder ? this.hitEffect.toPMF(eps) : pmfFromRollBuilder(this.hitEffect, eps) : PMF.delta(0, eps); + let critPMF = null; + let phit = pHit; + let pcrit = pCrit; + if (this.critEffect === null) { + critPMF = null; + phit += pcrit; + pcrit = 0; + } else { + let critBuilder; + if (this.critEffect) { + critBuilder = this.critEffect; + } else if (this.hitEffect instanceof ParsedRollBuilder) { + critPMF = null; + phit += pcrit; + pcrit = 0; + critBuilder = void 0; + } else { + critBuilder = this.hitEffect?.copy().doubleDice(); + } + if (critBuilder) { + critPMF = critBuilder instanceof ParsedRollBuilder ? critBuilder.toPMF(eps) : pmfFromRollBuilder(critBuilder, eps); + } + } + const missPMF = this.missEffect ? this.missEffect instanceof ParsedRollBuilder ? this.missEffect.toPMF(eps) : pmfFromRollBuilder(this.missEffect, eps) : PMF.delta(0, eps); + const mix = new Mixture(eps); + if (phit > 0) mix.add("hit", hitPMF, phit); + if (critPMF && pcrit > 0) mix.add("crit", critPMF, pcrit); + if (pmiss > 0) + mix.add(this.missEffect ? "missDamage" : "missNone", missPMF, pmiss); + return { + pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps), + check: this.check.toPMF(eps) ?? PMF.delta(0, eps), + hit: hitPMF ?? PMF.delta(0, eps), + crit: critPMF ?? PMF.delta(0, eps), + miss: missPMF ?? PMF.delta(0, eps), + weights: { hit: phit, crit: pcrit, miss: pmiss } + }; + } + // By default, create PMF with no pruning + toPMF(eps = 0) { + return this.resolve(eps).pmf; + } + get pmf() { + return this.toPMF(); + } + // By default, create query on PMF with no pruning + toQuery(eps = 0) { + return this.toPMF(eps).query(); + } +}; + +// src/builder/ac.ts +var ACBuilder = class _ACBuilder extends RollBuilder { + constructor(baseRoll, ac, attackConfig) { + super(baseRoll.getSubRollConfigs()); + if (attackConfig) { + this.attackConfig = { ...attackConfig, ac }; + } else { + this.attackConfig = { ac, critThreshold: 20 }; + } + } + onHit(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new AttackBuilder(this, damageRoll); + } + get critThreshold() { + return this.attackConfig.critThreshold; + } + // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? + critOn(threshold) { + const newConfig = { + ...this.attackConfig, + critThreshold: threshold + }; + return new _ACBuilder(this, this.attackConfig.ac, newConfig); + } + alwaysCrits() { + return new AlwaysCritBuilder( + this, + { + critThreshold: this.attackConfig.critThreshold, + ac: this.attackConfig.ac + }, + false + ); + } + // Legacy expressions + toExpression() { + const configs = this.getSubRollConfigs(); + const expression = new RollBuilder(configs).toExpression(); + return this.attackConfig.ac ? `(${expression} AC ${this.attackConfig.ac})` : expression; + } + toPMF(eps = 0) { + const ac = this.attackConfig.ac; + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + const d202 = d20RollPMF(rollType, rerollOne); + const staticMod = this.modifier; + const bonusPMFs = this.getBonusDicePMFs(this, eps); + const parts = [d202, ...bonusPMFs]; + let attackRollPMF = parts.length === 1 ? d202 : PMF.convolveMany(parts, eps); + if (staticMod !== 0) + attackRollPMF = attackRollPMF.mapDamage( + (rollValue) => rollValue + staticMod + ); + const out = /* @__PURE__ */ new Map(); + for (const rollValue of attackRollPMF.support()) { + const p = attackRollPMF.pAt(rollValue); + const key = rollValue >= ac ? rollValue : 0; + out.set(key, (out.get(key) || 0) + p); + } + return PMF.fromMap(out, eps); + } + copy() { + const baseCopy = new RollBuilder(this.getSubRollConfigs()); + const newConfig = { + ac: this.attackConfig.ac, + critThreshold: this.attackConfig.critThreshold + }; + return new _ACBuilder(baseCopy, newConfig.ac, newConfig); + } +}; +RollBuilder.prototype.ac = function(targetAC) { + if (isNaN(targetAC)) throw new Error("Invalid NaN value for targetAC"); + return new ACBuilder(this, targetAC); +}; + +// src/builder/save.ts +var SaveBuilder = class _SaveBuilder { + constructor(check, failureEffect, saveOutcome = "normal") { + this.check = check; + this.failureEffect = failureEffect; + this.saveOutcome = saveOutcome; + } + saveHalf() { + return new _SaveBuilder(this.check, this.failureEffect, "half"); + } + toExpression() { + const checkPart = this.check.toExpression(); + if (!this.failureEffect) return checkPart; + const failureEffectPart = this.failureEffect.toExpression(); + const result = `${checkPart} * (${failureEffectPart})`; + return this.saveOutcome === "half" ? `${result} save half` : result; + } + resolve(eps = EPS) { + const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities( + this.check + ); + const failPMF = this.failureEffect ? this.failureEffect instanceof ParsedRollBuilder ? this.failureEffect.toPMF(eps) : pmfFromRollBuilder(this.failureEffect) : PMF.delta(0); + const onSuccess = this.saveOutcome ?? "half"; + let successPMF = PMF.delta(0, eps); + if (onSuccess === "half") successPMF = failPMF.scaleDamage(0.5, "floor"); + const successLabel = onSuccess === "normal" ? "missNone" : "saveHalf"; + const failLabel = "saveFail"; + const baseMix = new Mixture(eps); + const mixture = baseMix.add(successLabel, successPMF, psuccess).add(failLabel, failPMF, pfail); + return { + pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps), + check: PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps), + saveFail: failPMF ?? PMF.delta(0, eps), + saveSuccess: successPMF ?? PMF.delta(0, eps), + weights: { success: psuccess, fail: pfail } + }; + } + // By default, create PMF with no pruning + toPMF(eps = 0) { + return this.resolve(eps).pmf; + } + get pmf() { + return this.toPMF(); + } + // By default, create query on PMF with no pruning + toQuery(eps = 0) { + return this.toPMF(eps).query(); + } +}; +function resolveProbabilities(check) { + const saveBonus = check.modifier; + const dc = check.saveDC; + const d20Type = check.rollType; + const baseReroll = check.baseReroll; + const die = d20RollPMF(d20Type, baseReroll > 0); + const faceP = /* @__PURE__ */ new Map(); + for (const [r, bin] of die) { + const pr = bin.p; + if (pr > 0) faceP.set(r, pr); + } + const eps = 0; + const bonusDicePMFs = check.getBonusDicePMFs(check, eps); + const bonusPMF = bonusDicePMFs.length > 0 ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.zero(eps); + let pSuccess = 0; + for (let r = 1; r <= 20; r++) { + const pr = faceP.get(r); + if (!pr) continue; + const need = dc - saveBonus - r; + pSuccess += pr * bonusPMF.tailProbGE(need); + } + const pFail = Math.max(0, 1 - pSuccess); + return { pSuccess, pFail }; +} + +// src/builder/dc.ts +var DCBuilder = class _DCBuilder extends RollBuilder { + constructor(baseRoll, saveConfig) { + super(baseRoll.getSubRollConfigs()); + this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 }; + } + dc(saveDC) { + if (this.rollType && this.rollType === "elven accuracy") { + throw new Error( + "Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead." + ); + } + return new _DCBuilder(this, { dc: saveDC }); + } + get saveDC() { + return this.saveConfig.dc; + } + add(anotherRoll) { + const newBuilder = super.add(anotherRoll); + return new _DCBuilder(newBuilder, this.saveConfig); + } + addRoll(count) { + const newBuilder = super.addRoll(count); + return new _DCBuilder(newBuilder, this.saveConfig); + } + onSaveFailure(...args) { + const damageRoll = RollBuilder.fromArgs(...args); + return new SaveBuilder(this, damageRoll); + } + withElvenAccuracy() { + throw new Error( + "Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks)." + ); + } + // Legacy expressions + toExpression() { + const subConfigs = this.getSubRollConfigs(); + const allConfigs = [...subConfigs]; + const expression = new RollBuilder(allConfigs).toExpression(); + return `(${expression} DC ${this.saveConfig.dc})`; + } + toPMF(eps = 0) { + const saveDC = this.saveDC; + const rollType = this.rollType; + const rerollOne = this.baseReroll > 0; + const d202 = d20RollPMF(rollType, rerollOne); + const staticMod = this.modifier; + const bonusDicePMFs = this.getBonusDiceConfigs().map( + (cfg) => pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps) + ); + const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); + let psuccess = 0; + for (const [r, bin] of d202) { + const pr = bin.p; + if (pr <= 0) continue; + const need = saveDC - staticMod - r; + psuccess += pr * bonusPMF.tailProbGE(need); + } + const pfail = Math.max(0, 1 - psuccess); + const m = /* @__PURE__ */ new Map([ + [0, psuccess > 0 ? psuccess : 0], + [1, pfail > 0 ? pfail : 0] + ]); + return PMF.fromMap(m, eps); + } +}; +RollBuilder.prototype.dc = function(saveDC) { + if (isNaN(saveDC)) throw new Error("Invalid NaN value for saveDC"); + return new DCBuilder(this).dc(saveDC); +}; + +export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, DCBuilder, HalfRollBuilder, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, RollBuilder, SaveBuilder, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls }; +//# sourceMappingURL=index.js.map +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/builder/index.js.map b/dist/builder/index.js.map new file mode 100644 index 0000000..7d9ec09 --- /dev/null +++ b/dist/builder/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/common/lru-cache.ts","../../src/common/types.ts","../../src/pmf/query.ts","../../src/pmf/pmf.ts","../../src/pmf/mixture.ts","../../src/common/errors.ts","../../src/parser/dice.ts","../../src/parser/parser.ts","../../src/builder/prob.ts","../../src/builder/d20.ts","../../src/builder/roll.ts","../../src/builder/factory.ts","../../src/builder/ast.ts","../../src/builder/attack.ts","../../src/builder/ac.ts","../../src/builder/save.ts","../../src/builder/dc.ts"],"names":["d","cumulativeSum","result","dfs","arr","d20","ac","staticMod","bonusDicePMFs","bonusPMF","pcrit","pmiss"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import { EPS, PMF } from \"../\";\n\nexport function d20PmfFromCdf(\n cdfPow: (k: number) => number,\n eps: number = EPS\n): PMF {\n const out = new Map();\n let prev = 0;\n for (let k = 1; k <= 20; k++) {\n const cur = cdfPow(k);\n const pk = cur - prev;\n if (pk > 0) {\n out.set(k, pk);\n }\n prev = cur;\n }\n\n return PMF.fromMap(out, eps);\n}\n","import { EPS, PMF, pmfCache } from \"../\";\nimport { d20PmfFromCdf } from \"./prob\";\nimport type { RollType } from \"./types\";\n\nconst cacheKeyMap: Record = {\n \"flat-flat\": \"d20\",\n \"flat-reroll\": \"hd20\",\n \"advantage-flat\": \"d20 > d20\",\n \"advantage-reroll\": \"hd20 > hd20\",\n \"disadvantage-flat\": \"d20 < d20\",\n \"disadvantage-reroll\": \"hd20 < hd20\",\n \"elven accuracy-flat\": \"d20 > d20 > d20\",\n \"elven accuracy-reroll\": \"hd20 > hd20 > hd20\",\n};\n\n/** Lift a single d20 PMF into advantage, disadvantage, or elven (triple-advantage). */\nexport function d20RollPMF(\n rollType: RollType | undefined,\n rerollOne: boolean = false\n): PMF {\n rollType = rollType || \"flat\";\n const cacheKeyLookup = `${rollType}-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cacheKey = cacheKeyMap[cacheKeyLookup];\n if (!cacheKey) {\n throw new Error(`Invalid roll type: ${rollType}`);\n }\n\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const base = d20PMF(rerollOne);\n if (!rollType || rollType === \"flat\") {\n pmfCache.set(cacheKey, base);\n return base;\n }\n\n const p: number[] = new Array(21).fill(0); // indices 1..20\n for (const [r, rec] of base) {\n const pr = typeof rec === \"number\" ? rec : rec.p;\n if (r >= 1 && r <= 20) p[r] = pr;\n }\n const F: number[] = new Array(21).fill(0);\n for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k];\n\n const eps = 0;\n let result = base;\n if (rollType === \"advantage\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps);\n } else if (rollType === \"elven accuracy\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps);\n } else if (rollType === \"disadvantage\") {\n result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps);\n }\n\n pmfCache.set(cacheKey, result);\n return result;\n}\n\nexport function d20PMF(rerollOne: boolean): PMF {\n const cacheKey = `flat-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const m = new Map();\n const base = 1 / 20;\n const rerollShare = base * base;\n if (!rerollOne) {\n for (let r = 1; r <= 20; r++) {\n m.set(r, base);\n }\n } else {\n for (let r = 1; r <= 20; r++) {\n m.set(r, (r === 1 ? 0 : base) + rerollShare);\n }\n }\n const result = PMF.fromMap(m, EPS);\n pmfCache.set(cacheKey, result);\n return result;\n}\n","import type { ACBuilder } from \"./ac\";\nimport type { CritConfig } from \"../common/types\";\nimport type { DCBuilder } from \"./dc\";\nimport { parse } from \"../parser/parser\";\nimport type { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { astFromRollConfigs, pmfFromRollBuilder } from \"./ast\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport type { ExpressionNode, KeepNode, SumNode } from \"./nodes\";\nimport type { RollConfig, RollType } from \"./types\";\n\nexport const defaultConfig: RollConfig = {\n count: 1,\n sides: 0,\n modifier: 0,\n reroll: 0,\n explode: 0,\n minimum: 0,\n bestOf: 0,\n keep: undefined,\n rollType: \"flat\",\n};\n\nconst rollConfigsEqual = (a: RollConfig, b: RollConfig) => {\n return (\n a.count === b.count &&\n a.sides === b.sides &&\n a.modifier === b.modifier &&\n a.reroll === b.reroll &&\n a.explode === b.explode &&\n a.minimum === b.minimum &&\n a.bestOf === b.bestOf &&\n a.keep === b.keep &&\n a.rollType === b.rollType\n );\n};\n\nconst configComplexityScore = (config: RollConfig) => {\n return (\n (config.reroll > 0 ? 1 : 0) +\n (config.explode > 0 ? 1 : 0) +\n (config.minimum > 0 ? 1 : 0) +\n (config.bestOf > 0 ? 1 : 0) +\n (config.keep !== undefined ? 1 : 0) +\n (config.rollType !== \"flat\" ? 1 : 0)\n );\n};\n\n// Fluent builder for dice to create PMFs with an AST\nexport class RollBuilder {\n protected readonly subRollConfigs: readonly RollConfig[];\n\n constructor(countOrConfigs: number | readonly RollConfig[] = 1) {\n if (typeof countOrConfigs === \"number\") {\n const count = countOrConfigs;\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n this.subRollConfigs = [\n { ...defaultConfig, count, isSubtraction: count < 0 },\n ];\n } else {\n this.subRollConfigs = countOrConfigs.map((c) => ({ ...c }));\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n protected get lastConfig() {\n return this.subRollConfigs[this.subRollConfigs.length - 1];\n }\n\n hasHiddenState(): boolean {\n return false;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.subRollConfigs.map((c: RollConfig) => ({ ...c }));\n }\n\n // for testing\n static fromConfig(config: Partial): RollBuilder {\n return new RollBuilder([{ ...defaultConfig, ...config }]);\n }\n\n static fromConfigs(configs: Partial[]): RollBuilder {\n return new RollBuilder(\n configs.map((config) => ({ ...defaultConfig, ...config }))\n );\n }\n\n static fromArgs(...args: any[]): RollBuilder {\n if (args.length === 1) {\n const arg = args[0];\n if (typeof arg === \"number\") {\n if (isNaN(arg)) throw new Error(\"Invalid NaN value for argument\");\n return new RollBuilder(0).plus(arg);\n }\n if (typeof arg === \"string\") {\n return new ParsedRollBuilder(arg);\n }\n if (arg instanceof RollBuilder) {\n return arg;\n }\n }\n\n if (args.length === 2 || args.length === 3) {\n const [count, sidesOrDie, modifier] = args;\n\n if (typeof count !== \"number\") {\n throw new Error(\"First argument must be a number for multi-arg call\");\n }\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count argument\");\n\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) {\n const result = new RollBuilder(0);\n return modifier !== undefined ? result.plus(modifier) : result;\n }\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return modifier !== undefined\n ? resultBuilder.plus(modifier)\n : resultBuilder;\n } else if (typeof sidesOrDie === \"number\" || sidesOrDie === undefined) {\n if (typeof sidesOrDie === \"number\" && isNaN(sidesOrDie))\n throw new Error(\"Invalid NaN value for sides argument\");\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return modifier !== undefined ? builder.plus(modifier) : builder;\n }\n }\n\n throw new Error(`Invalid arguments passed: ${args.join(\", \")}`);\n }\n\n // --- Core Dice Methods ---\n d(sides: number | undefined): RollBuilder {\n if (sides !== undefined && isNaN(sides))\n throw new Error(\"Invalid NaN value for sides\");\n if (sides === undefined) return this;\n if (this.lastConfig.sides && this.lastConfig.sides > 0) {\n throw new Error(\"Cannot add a die after adding a die\");\n }\n if (sides === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].sides = sides;\n return this.create(newConfigs);\n }\n\n plus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n plus(count: number, die: RollBuilder): RollBuilder;\n plus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n if (typeof modOrRoll === \"number\" && isNaN(modOrRoll))\n throw new Error(\"Invalid NaN value for modOrRoll\");\n if (die instanceof RollBuilder && typeof modOrRoll === \"number\") {\n if (die.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const count = modOrRoll;\n const subRollConfigs = die.getSubRollConfigs();\n if (subRollConfigs.length === 0) return this;\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let rollToAdd = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = rollToAdd\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n rollToAdd = new RollBuilder(negatedConfigs);\n }\n return this.add(rollToAdd);\n }\n\n if (die !== undefined) {\n throw new Error(\"Invalid arguments to plus()\");\n }\n\n if (modOrRoll === undefined) return this;\n if (typeof modOrRoll === \"number\") {\n if (modOrRoll === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].modifier += modOrRoll;\n return this.create(newConfigs);\n }\n return this.add(modOrRoll as RollBuilder);\n }\n\n minus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n minus(count: number, die: RollBuilder): RollBuilder;\n minus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n const isNumber = typeof modOrRoll === \"number\";\n const dieIsRoll = die instanceof RollBuilder;\n if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die);\n\n if (die !== undefined) throw new Error(\"Invalid arguments to minus()\");\n if (modOrRoll === undefined) return this;\n\n return isNumber\n ? this.plus(-modOrRoll)\n : this.plus(-1, modOrRoll as RollBuilder);\n }\n\n /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */\n reroll(value: number): RollBuilder {\n if (isNaN(value)) throw new Error(\"Invalid NaN value for reroll\");\n if (value === this.lastConfig.reroll) return this;\n\n const newConfigs = this.getSubRollConfigs();\n\n newConfigs[newConfigs.length - 1].reroll = value;\n return this.create(newConfigs);\n }\n\n /** Set finite explode count for max-face explosions (Infinity allowed). */\n explode(count: number | undefined = Infinity): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for explode count\");\n if (count === undefined) return this;\n if (count === 0) return this;\n if (count < 0) throw new Error(\"Explode count must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].explode = count;\n return this.create(newConfigs);\n }\n\n /** Apply per-die minimum value (min > 0). */\n minimum(val: number | undefined): RollBuilder {\n if (val !== undefined && isNaN(val))\n throw new Error(\"Invalid NaN value for minimum\");\n if (val === undefined) return this;\n if (val === 0) return this;\n if (val < 0) throw new Error(\"Minimum value must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].minimum = val + 1;\n return this.create(newConfigs);\n }\n\n bestOf(count: number | undefined): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for bestOf count\");\n if (count === undefined) return this;\n if (count <= 0) throw new Error(\"Best of count must be > 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].bestOf = count;\n return this.create(newConfigs);\n }\n\n keepHighest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"highest\" };\n return this.create(newConfigs);\n }\n\n keepLowest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"lowest\" };\n return this.create(newConfigs);\n }\n\n keepHighestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighestAll\");\n const currentAST = this.toAST();\n // Wrap in SumNode to represent trials, then KeepNode\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"highest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kh${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n keepLowestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowestAll\");\n const currentAST = this.toAST();\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"lowest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kl${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n withAdvantage(): RollBuilder {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"advantage\";\n return this.create(newConfigs);\n }\n\n withDisadvantage(): RollBuilder {\n const configs = this.getSubRollConfigs();\n configs[configs.length - 1].rollType = \"disadvantage\";\n return this.create(configs);\n }\n\n add(anotherRoll: RollBuilder | undefined): RollBuilder {\n if (anotherRoll === undefined) return this;\n if (anotherRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll).\"\n );\n }\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n withBonus(anotherRoll: RollBuilder): RollBuilder {\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n addRoll(count: number = 1): RollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n const configs = [\n ...this.subRollConfigs,\n {\n ...defaultConfig,\n count,\n isSubtraction: count < 0,\n },\n ];\n return this.create(configs);\n }\n\n scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n const newConfigs = this.getSubRollConfigs().map((config) => {\n if (!config.sides || config.sides <= 0) return config;\n return { ...config, count: config.count * scaleInt };\n });\n return this.create(newConfigs);\n }\n\n doubleDice(): RollBuilder {\n return this.scaleDice(2);\n }\n\n alwaysHits() {\n return new AlwaysHitBuilder(this);\n }\n\n alwaysCrits() {\n return new AlwaysCritBuilder(this);\n }\n\n copy(): RollBuilder {\n return this.create(this.getSubRollConfigs());\n }\n\n // --- Dice Shortcut Methods ---\n d4 = () => this.d(4);\n d6 = () => this.d(6);\n d8 = () => this.d(8);\n d10 = () => this.d(10);\n d12 = () => this.d(12);\n d20 = () => this.d(20);\n d100 = () => this.d(100);\n\n withElvenAccuracy() {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"elven accuracy\";\n return this.create(newConfigs);\n }\n\n toExpression(): string {\n const originalDiceConfigs = this.subRollConfigs.filter(\n (config) => config.sides && config.sides > 0\n );\n\n type Group = { config: RollConfig; totalCount: number };\n const configGroups = new Map();\n\n for (const config of originalDiceConfigs) {\n const keyConfig: Partial = { ...config };\n delete keyConfig.count;\n delete keyConfig.modifier;\n const key = JSON.stringify(keyConfig);\n\n const existingGroup = configGroups.get(key);\n if (existingGroup) {\n existingGroup.totalCount += config.count;\n } else {\n configGroups.set(key, { config, totalCount: config.count });\n }\n }\n\n const rootConfig = this.getRootDieConfig();\n const groupedConfigs = Array.from(configGroups.values());\n let rootD20Group: Group | undefined;\n\n if (rootConfig && rootConfig.sides === 20) {\n const rootIndex = groupedConfigs.findIndex(\n ({ config }) =>\n rollConfigsEqual(config, rootConfig) &&\n JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep)\n );\n\n if (rootIndex !== -1) {\n rootD20Group = groupedConfigs.splice(rootIndex, 1)[0];\n }\n }\n\n const sortedDiceConfigs = groupedConfigs\n .map(({ config, totalCount }) => ({\n ...config,\n count: totalCount,\n }))\n .sort((a, b) => {\n const aHasPriority = a.reroll > 0 || a.minimum > 0;\n const bHasPriority = b.reroll > 0 || b.minimum > 0;\n if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1;\n if (b.sides !== a.sides) return b.sides - a.sides;\n return configComplexityScore(b) - configComplexityScore(a);\n });\n\n const diceConfigs = rootD20Group\n ? [\n { ...rootD20Group.config, count: rootD20Group.totalCount },\n ...sortedDiceConfigs,\n ]\n : sortedDiceConfigs;\n\n const totalModifier = this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n if (diceConfigs.length === 0) return totalModifier.toString();\n\n const rootDieConfig = this.getRootDieConfig();\n const newRootConfig = rootDieConfig\n ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig))\n : undefined;\n\n // Generate dice expressions without individual modifiers\n const diceExpressions = diceConfigs.map((config) =>\n this.configToSingleExpressionWithoutModifier(\n config,\n config === newRootConfig\n )\n );\n\n // Join dice expressions with appropriate operators based on their count\n let result = \"\";\n for (let i = 0; i < diceExpressions.length; i++) {\n const config = diceConfigs[i];\n const expression = diceExpressions[i];\n\n if (i === 0) {\n result = (config.isSubtraction ? \"-\" : \"\") + expression;\n\n // Add constants right after the root d20 die (if it's a d20)\n if (config.sides === 20 && totalModifier !== 0) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else result += ` - ${Math.abs(totalModifier)}`;\n }\n } else {\n // Use minus sign for negative subtraction, plus sign otherwise\n const operator = config.isSubtraction ? \" - \" : \" + \";\n result += operator + expression;\n }\n }\n\n // If constants weren't added after d20, add them at the end\n if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`;\n }\n\n return result.replace(/\\+ -/g, \"-\");\n }\n\n toPMF(eps: number = 0): PMF {\n // Main AST entry point\n return pmfFromRollBuilder(this, eps);\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n\n toAST(): ExpressionNode {\n const configs = this.getSubRollConfigs();\n return (\n astFromRollConfigs(configs) ||\n ({ type: \"constant\", value: 0 } as ExpressionNode)\n );\n }\n\n private configToSingleExpressionWithoutModifier(\n config: RollConfig,\n isRootDie: boolean\n ): string {\n if (!config.sides || config.sides <= 0) return \"\";\n\n let baseDie = `d${config.sides}`;\n\n if (config.reroll > 0) {\n if (config.minimum > 0 && config.explode > 0) {\n // Complex single roll case: minimum + explode + reroll\n // Apply reroll after minimum is applied\n } else if (config.minimum > 0) {\n // When there's a minimum but no explode, use descending order and apply before minimum\n for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`;\n } else {\n // When there's no minimum, use ascending order\n for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`;\n }\n }\n\n if (config.minimum > 0) {\n if (config.reroll > 0 && !config.explode) {\n baseDie = `${config.minimum}>(${baseDie})`;\n } else {\n baseDie = `${config.minimum}>${baseDie}`;\n }\n if (config.reroll > 0 && config.explode > 0) {\n for (let i = 1; i <= config.reroll; i++) {\n baseDie += ` reroll ${i}`;\n }\n }\n }\n\n // Check for hd20 shorthand AFTER adding explode\n if (baseDie === \"d20 reroll 1\" && config.minimum <= 1) baseDie = \"hd20\";\n\n let mainExpression = \"\";\n switch (config.rollType) {\n case \"advantage\":\n mainExpression = `${baseDie} > ${baseDie}`;\n break;\n case \"disadvantage\":\n mainExpression = `${baseDie} < ${baseDie}`;\n break;\n case \"elven accuracy\":\n mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`;\n break;\n case \"flat\":\n if (config.keep) {\n const mode = config.keep.mode === \"highest\" ? \"kh\" : \"kl\";\n const baseDieExpression =\n this.configToSingleExpressionWithoutModifier(\n {\n ...config,\n count: config.count,\n modifier: 0,\n rollType: \"flat\",\n keep: undefined,\n },\n false\n );\n mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`;\n } else {\n const isComplex = baseDie.length > `d${config.sides}`.length;\n const isHalflingShorthand = baseDie === \"hd20\";\n const isD20Shorthand = baseDie === \"d20\" && isRootDie;\n const hasMinimum = config.minimum > 0;\n const hasReroll = config.reroll > 0;\n // For negative subtraction, use absolute value for display\n // For negative counts from factory function, treat as 1 (legacy behavior)\n const effectiveCount = config.isSubtraction\n ? Math.abs(config.count)\n : config.count < 0\n ? 1\n : Math.abs(config.count);\n\n if (effectiveCount > 1) {\n const shouldAddParentheses = isComplex;\n mainExpression = shouldAddParentheses\n ? `${effectiveCount}(${baseDie})`\n : `${effectiveCount}${baseDie}`;\n } else if (effectiveCount === 1) {\n const needsParens = hasReroll && hasMinimum;\n if (config.isSubtraction) {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n } else if (\n isComplex ||\n isHalflingShorthand ||\n isD20Shorthand ||\n config.count < 0\n ) {\n mainExpression = needsParens ? `1(${baseDie})` : baseDie;\n } else {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n }\n } else {\n mainExpression = baseDie;\n }\n }\n if (config.bestOf && config.count && config.bestOf < config.count) {\n mainExpression += `kh${config.bestOf}`;\n }\n break;\n }\n\n return mainExpression;\n }\n\n getRootDieConfig(): RollConfig | undefined {\n const configs = this.subRollConfigs;\n return configs.find((config) => config.sides > 0) || configs[0];\n }\n\n getAllDieConfigs(): readonly RollConfig[] {\n return this.getSubRollConfigs();\n }\n\n getBonusDiceConfigs(): RollConfig[] {\n const allConfigs = this.subRollConfigs;\n const rootConfig =\n allConfigs.find((config) => config.sides > 0) || allConfigs[0];\n if (!rootConfig) return [];\n return allConfigs\n .filter((config) => config.sides > 0)\n .filter((config) => config !== rootConfig);\n }\n\n getBonusDicePMFs(check: RollBuilder, eps: number = 0): PMF[] {\n return check\n .getBonusDiceConfigs()\n .map((config) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([config]), eps)\n );\n }\n\n get modifier(): number {\n return this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n }\n\n get rollType(): RollType {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.rollType || \"flat\";\n }\n\n get baseReroll(): number {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.reroll || 0;\n }\n\n half(): HalfRollBuilder {\n return new HalfRollBuilder(this);\n }\n\n /**\n * Scale this roll's result by `numerator / denominator`, rounding each outcome.\n * A general, composable form of {@link half} — used to model damage-type resistance\n * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`).\n * Compose several of these (and plain rolls) into one payload with {@link sumRolls}.\n */\n scaleResult(\n numerator: number,\n denominator: number = 1,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): ScaleRollBuilder {\n return new ScaleRollBuilder(this, numerator, denominator, rounding);\n }\n\n // Create a \"max of N rolls\" version of this roll for crit damage with keep operations\n maxOf(count: number): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this, count);\n }\n\n // These methods are implemented via prototype augmentation in ac.ts and dc.ts\n // They are declared here to provide proper TypeScript types\n ac(_targetAC: number): ACBuilder {\n throw new Error(\"ac() should be implemented via prototype augmentation\");\n }\n\n dc(_saveDC: number): DCBuilder {\n throw new Error(\"dc() should be implemented via prototype augmentation\");\n }\n}\n\nexport class HalfRollBuilder extends RollBuilder {\n constructor(private readonly innerRoll: RollBuilder) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n // No need to override create if we don't expose RollBuilder methods that use it,\n // but HalfRollBuilder extends RollBuilder so it does.\n // However, HalfRollBuilder seems to just wrap another roll.\n // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder?\n // No, RollBuilder.plus returns RollBuilder.\n // The inheritance here is a bit tricky.\n // Existing code for HalfRollBuilder doesn't seem to implement plus/etc.\n // So .plus() on a HalfRollBuilder would return a RollBuilder (base class).\n // Which is fine.\n // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that.\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const innerExpression = this.innerRoll.toExpression();\n return `(${innerExpression}) // 2`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"half\",\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): HalfRollBuilder {\n return new HalfRollBuilder(this.innerRoll.copy());\n }\n}\n\n/**\n * A roll whose result is scaled by `numerator / denominator` and rounded — the composable\n * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or\n * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls.\n */\nexport class ScaleRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly numerator: number,\n private readonly denominator: number = 1,\n private readonly rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const inner = this.innerRoll.toExpression();\n if (this.denominator === 1) return `${this.numerator} * (${inner})`;\n if (this.numerator === 1) return `(${inner}) // ${this.denominator}`;\n return `(${inner}) * ${this.numerator} // ${this.denominator}`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"scale\",\n numerator: this.numerator,\n denominator: this.denominator,\n rounding: this.rounding,\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): ScaleRollBuilder {\n return new ScaleRollBuilder(\n this.innerRoll.copy(),\n this.numerator,\n this.denominator,\n this.rounding\n );\n }\n}\n\nexport class MaxOfRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly count: number,\n private readonly diceCount?: number,\n private readonly diceSides?: number\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n // Use the stored dice info to create the expression directly\n if (this.diceCount && this.diceSides) {\n return `max${this.count}(${this.diceCount}d${this.diceSides})`;\n }\n\n // If no stored dice info, fallback to simple max expression\n return `max${this.count}(?d?)`;\n }\n\n toAST(): ExpressionNode {\n // Use the stored dice info if available\n if (this.diceCount && this.diceSides) {\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: this.diceCount,\n child: { type: \"die\", sides: this.diceSides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n\n // Fallback: try to get from innerRoll\n try {\n const configs = this.innerRoll.getSubRollConfigs();\n if (configs.length === 1 && configs[0].sides) {\n const config = configs[0];\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: config.count,\n child: { type: \"die\", sides: config.sides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n } catch {\n // Last resort: try parsing the expression (though this shouldn't work with current RollBuilder)\n }\n\n // Fallback - this shouldn't happen in normal usage\n throw new Error(\n `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`\n );\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this.innerRoll.copy(), this.count);\n }\n}\n\nexport class AlwaysHitBuilder extends RollBuilder {\n readonly attackConfig: CritConfig;\n\n constructor(baseRoll: RollBuilder, attackConfig?: CritConfig) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysHitBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(critThreshold: number): AlwaysHitBuilder {\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(this, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(this, undefined, true);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysHitBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(baseCopy, newConfig);\n }\n}\n\nexport class AlwaysCritBuilder extends RollBuilder {\n readonly attackConfig: CritConfig & { ac?: number };\n readonly fromAlwaysHit: boolean;\n\n constructor(\n baseRoll: RollBuilder,\n attackConfig?: CritConfig & { ac?: number },\n fromAlwaysHit: boolean = false\n ) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysCritBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n critOn(critThreshold: number): AlwaysCritBuilder {\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysCritBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit);\n }\n}\n\nexport class ParsedRollBuilder extends RollBuilder {\n private readonly cachedPMF: PMF;\n private readonly originalExpression: string;\n\n constructor(expression: string) {\n super([]); // Empty configs since we're bypassing the normal builder flow\n this.originalExpression = expression;\n this.cachedPMF = parse(expression, 0);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n override toPMF(_eps: number = 0): PMF {\n // Return the pre-computed PMF, ignoring epsilon for now\n // The parse() function was already called with eps=0\n return this.cachedPMF;\n }\n\n override toExpression(): string {\n return this.originalExpression;\n }\n\n override toAST(): ExpressionNode {\n // Since we don't have the actual AST structure, return a constant node\n // This is a limitation but shouldn't matter for terminal damage expressions\n throw new Error(\n \"ParsedRollBuilder does not support AST conversion. Use the builder API instead.\"\n );\n }\n\n override copy(): ParsedRollBuilder {\n return new ParsedRollBuilder(this.originalExpression);\n }\n\n override doubleDice(): ParsedRollBuilder {\n throw new Error(\n \"ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead.\"\n );\n }\n}\n\nexport class PooledRollBuilder extends RollBuilder {\n constructor(\n private readonly baseAST: ExpressionNode,\n private readonly baseExpression: string,\n configs: readonly RollConfig[] = []\n ) {\n // Initialize with empty config if none provided\n super(configs.length > 0 ? configs : 0);\n }\n\n protected create(configs: readonly RollConfig[]): PooledRollBuilder {\n // This is the key fix: we preserve the baseAST and baseExpression\n // and only update the configs\n return new PooledRollBuilder(this.baseAST, this.baseExpression, configs);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override d(_sides: number | undefined): RollBuilder {\n throw new Error(\"Cannot add dice to a pooled roll. The pool is finalized.\");\n }\n\n override reroll(_value: number): RollBuilder {\n throw new Error(\"Cannot set reroll on a pooled roll.\");\n }\n\n override explode(_count: number | undefined = Infinity): RollBuilder {\n throw new Error(\"Cannot set explode on a pooled roll.\");\n }\n\n override minimum(_val: number | undefined): RollBuilder {\n throw new Error(\"Cannot set minimum on a pooled roll.\");\n }\n\n override bestOf(_count: number | undefined): RollBuilder {\n throw new Error(\"Cannot set bestOf on a pooled roll.\");\n }\n\n override keepHighest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling.\"\n );\n }\n\n override keepLowest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling.\"\n );\n }\n\n override withAdvantage(): RollBuilder {\n throw new Error(\"Cannot set advantage on a pooled roll.\");\n }\n\n override withDisadvantage(): RollBuilder {\n throw new Error(\"Cannot set disadvantage on a pooled roll.\");\n }\n\n override withElvenAccuracy(): RollBuilder {\n throw new Error(\"Cannot set elven accuracy on a pooled roll.\");\n }\n\n override toAST(): ExpressionNode {\n const configsAST = super.toAST();\n\n // Check if configsAST is effectively zero/empty\n const isZero = configsAST.type === \"constant\" && configsAST.value === 0;\n\n if (isZero) {\n return this.baseAST;\n }\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [\n { node: this.baseAST, sign: 1 },\n { node: configsAST, sign: 1 },\n ];\n\n return { type: \"add\", children };\n }\n\n override toExpression(): string {\n const configsExpression = super.toExpression();\n\n // If no configs added, just return base expression\n if (configsExpression === \"0\") {\n return this.baseExpression;\n }\n\n // Clean up the join\n if (configsExpression.startsWith(\"-\")) {\n // If it's a negative number/expression, format as \" - value\"\n // configsExpression is like \"-2\" or \"-1d6\"\n return `${this.baseExpression} - ${configsExpression.substring(1)}`;\n }\n return `${this.baseExpression} + ${configsExpression}`;\n }\n\n override copy(): PooledRollBuilder {\n return new PooledRollBuilder(\n this.baseAST,\n this.baseExpression,\n this.getSubRollConfigs()\n );\n }\n\n override scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n // Scale the base pool (treat it as a die/unit)\n // We wrap the base AST in a SumNode\n const newBaseAST: SumNode = {\n type: \"sum\",\n count: scaleInt,\n child: this.baseAST,\n };\n const newBaseExpr =\n scaleInt === 1\n ? this.baseExpression\n : `${scaleInt}(${this.baseExpression})`;\n\n // We preserve the existing modifiers (subRollConfigs) without scaling them,\n // because scaleDice() generally only scales \"dice\", not flat modifiers.\n // Since we forbid adding dice to PooledRollBuilder, subRollConfigs are only modifiers.\n return new PooledRollBuilder(\n newBaseAST,\n newBaseExpr,\n this.getSubRollConfigs()\n );\n }\n\n times(count: number): PooledRollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for times\");\n if (Math.floor(count) !== count)\n throw new Error(\"times() requires an integer\");\n if (count < 0) throw new Error(\"times() requires a non-negative integer\");\n\n // We wrap the current state (base + modifiers) into a new pool repeated N times\n const currentAST = this.toAST();\n const currentExpr = this.toExpression();\n\n const sumNode: SumNode = {\n type: \"sum\",\n count,\n child: currentAST,\n };\n\n const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`;\n\n return new PooledRollBuilder(sumNode, newExpr);\n }\n}\n\n/**\n * An additive composite of independent rolls that preserves each part's AST — the piece\n * that lets a scaled/halved sub-roll (which the flat `.plus()` merge would otherwise drop)\n * sit beside plain rolls in one damage payload. Its PMF convolves the parts; its expression\n * joins them with ` + `. Built via {@link sumRolls}; terminal (used as an onHit/onCrit/\n * onSaveFailure payload), so it reports hidden state to reject accidental flat merges.\n */\nclass CompositeSumRollBuilder extends RollBuilder {\n constructor(private readonly parts: readonly RollBuilder[]) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override getSubRollConfigs(): readonly RollConfig[] {\n return [];\n }\n\n override toAST(): ExpressionNode {\n return {\n type: \"add\",\n children: this.parts.map((p) => ({\n node: p.toAST(),\n sign: 1 as const,\n })),\n };\n }\n\n override toExpression(): string {\n const exprs = this.parts\n .map((p) => p.toExpression())\n .filter((e) => e && e !== \"0\");\n if (exprs.length === 0) return \"0\";\n let result = exprs[0];\n for (let i = 1; i < exprs.length; i++) {\n const e = exprs[i];\n result += e.startsWith(\"-\") ? ` - ${e.substring(1)}` : ` + ${e}`;\n }\n return result.replace(/\\+ -/g, \"-\");\n }\n\n override toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n override copy(): CompositeSumRollBuilder {\n return new CompositeSumRollBuilder(this.parts.map((p) => p.copy()));\n }\n}\n\n/**\n * Combine several rolls into one additive payload whose PMF is their convolution and whose\n * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry\n * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance\n * and vulnerability survive into both the distribution and the rendered expression.\n * Empty parts collapse to `0`; a single part is returned unwrapped.\n */\nexport function sumRolls(parts: readonly RollBuilder[]): RollBuilder {\n const meaningful = parts.filter((p): p is RollBuilder => p !== undefined);\n if (meaningful.length === 0) return new RollBuilder(0);\n if (meaningful.length === 1) return meaningful[0];\n return new CompositeSumRollBuilder(meaningful);\n}\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { RollBuilder } from \"./roll\";\nimport type { RollFactory } from \"./types\";\n\nconst rollFn = (\n count: number,\n sidesOrDie?: number | RollBuilder,\n modifier?: number\n): RollBuilder => {\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n // roll(2, d6, 5)\n // Create a new config, using the base die's config but overriding the count\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier);\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return resultBuilder.plus(modifier);\n } else {\n // roll(2, 6, 5)\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return builder.plus(modifier);\n }\n};\n\nrollFn.d = (sides: number | string): RollBuilder => {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n};\nrollFn.hd20 = (): RollBuilder => new RollBuilder(1).d20().reroll(1);\nrollFn.d4 = (): RollBuilder => new RollBuilder(1).d4();\nrollFn.d6 = (): RollBuilder => new RollBuilder(1).d6();\nrollFn.d8 = (): RollBuilder => new RollBuilder(1).d8();\nrollFn.d10 = (): RollBuilder => new RollBuilder(1).d10();\nrollFn.d12 = (): RollBuilder => new RollBuilder(1).d12();\nrollFn.d20 = (): RollBuilder => new RollBuilder(1).d20();\nrollFn.d100 = (): RollBuilder => new RollBuilder(1).d100();\nrollFn.flat = (n: number): RollBuilder => new RollBuilder(0).plus(n);\n\nexport function d(sides: number | string): RollBuilder {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n}\n\nexport const d4 = new RollBuilder(1).d4();\nexport const d6 = new RollBuilder(1).d6();\nexport const d8 = new RollBuilder(1).d8();\nexport const d10 = new RollBuilder(1).d10();\nexport const d12 = new RollBuilder(1).d12();\nexport const d20 = new RollBuilder(1).d20();\nexport const hd20 = new RollBuilder(1).d20().reroll(1);\nexport const d100 = new RollBuilder(1).d100();\nexport const flat = (n: number) => new RollBuilder(0).plus(n);\n\nexport const roll: RollFactory = rollFn as RollFactory;\n\nexport const builderPMFCache = new LRUCache(1000);\n","import { LRUCache, PMF } from \"../\";\nimport { d20RollPMF } from \"./d20\";\nimport { builderPMFCache } from \"./factory\";\nimport type {\n AddNode,\n ConstantNode,\n D20RollNode,\n DieNode,\n ExpressionNode,\n KeepNode,\n MaxOfNode,\n SumNode,\n} from \"./nodes\";\nimport type { RollBuilder } from \"./roll\";\nimport type { RollConfig } from \"./types\";\n\n// For now, default to 0 epsilon. Later we can tighten to EPS.\nconst defaultEps = 0;\n\nconst singleDiePMFCache = new LRUCache(1000);\n\nexport function astFromRollConfigs(\n configs: readonly RollConfig[]\n): ExpressionNode | undefined {\n // TODO add cache for this\n if (!configs || configs.length === 0) return undefined;\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [];\n let constantSum = 0;\n\n for (const cfg of configs) {\n const sign: 1 | -1 = cfg.isSubtraction || cfg.count < 0 ? -1 : 1;\n const count = Math.abs(cfg.count || 0);\n\n constantSum += cfg.modifier || 0;\n\n if ((cfg.sides || 0) <= 0) continue;\n\n const die: DieNode = {\n type: \"die\",\n sides: cfg.sides,\n reroll: cfg.reroll > 0 ? cfg.reroll : undefined,\n minimum: cfg.minimum > 0 ? cfg.minimum : undefined,\n explode:\n cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0\n ? cfg.explode\n : undefined,\n };\n\n let node: ExpressionNode = die;\n\n let appliedRollType = false;\n if (cfg.rollType && cfg.rollType !== \"flat\") {\n if (cfg.sides === 20) {\n node = {\n type: \"d20Roll\",\n rollType: cfg.rollType,\n child: node,\n } as D20RollNode;\n } else {\n const n = cfg.rollType === \"elven accuracy\" ? 3 : 2;\n const mode = cfg.rollType === \"disadvantage\" ? \"lowest\" : \"highest\";\n const base: SumNode = { type: \"sum\", count: n, child: node };\n node = { type: \"keep\", mode, count: 1, child: base } as KeepNode;\n }\n appliedRollType = true;\n }\n\n if (cfg.rollType === \"flat\" && cfg.keep && cfg.keep.total > 0) {\n const baseCount = Math.max(1, Math.floor(Math.abs(count || 1)));\n const trials = Math.max(1, Math.floor(cfg.keep.total));\n const k = Math.max(0, Math.floor(cfg.keep.count));\n\n // For keep-highest of 1, always treat as trials-of-sums: max over trial sums\n if (k === 1 && cfg.keep.mode === \"highest\") {\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n node = {\n type: \"maxOf\",\n count: trials,\n child: perTrial,\n } as MaxOfNode;\n }\n } else if (trials === baseCount) {\n // Classic pool: keep K of N faces from N iid dice\n const base: SumNode = { type: \"sum\", count: trials, child: node };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: base,\n } as KeepNode;\n } else {\n // General trials-of-sums: trials of (baseCount dice sum), keep K trial sums\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n const trialPool: SumNode = {\n type: \"sum\",\n count: trials,\n child: perTrial,\n };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: trialPool,\n } as KeepNode;\n }\n }\n } else {\n const c = appliedRollType ? 1 : Math.max(1, count || 1);\n node = { type: \"sum\", count: c, child: node } as SumNode;\n }\n\n children.push({ node, sign });\n }\n\n if (children.length === 0) {\n return { type: \"constant\", value: constantSum } as ConstantNode;\n }\n\n const add: AddNode = { type: \"add\", children };\n if (constantSum !== 0)\n add.children.push({\n node: { type: \"constant\", value: constantSum },\n sign: 1,\n });\n return add;\n}\n\nexport function resolve(node: ExpressionNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(node);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n const result = ((): PMF => {\n switch (node.type) {\n case \"constant\":\n return PMF.delta(node.value, eps);\n\n case \"die\": {\n return resolveSingleDie(node, eps);\n }\n\n case \"sum\": {\n const base = resolve(node.child, eps);\n const n = Math.max(0, Math.floor(node.count));\n if (n === 0) return PMF.delta(0, eps);\n if (n === 1) return base;\n return base.power(n, eps);\n }\n\n case \"add\": {\n let shift = 0;\n const parts: PMF[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n shift += c.sign * c.node.value;\n } else {\n const p = resolve(c.node, eps);\n parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v));\n }\n }\n if (parts.length === 0) return PMF.delta(shift, eps);\n let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps);\n if (shift !== 0) res = res.mapDamage((v) => v + shift);\n return res;\n }\n\n case \"keep\": {\n const totalTrials = getTotalCount(node);\n const keepCount = Math.max(0, Math.min(node.count, totalTrials));\n if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps);\n\n // Resolve the per-trial PMF (the child of the Sum inside Keep)\n const perTrialNode = node.child.child; // Sum(child: perTrial)\n const perTrialPMF = resolve(perTrialNode, eps);\n\n return keepSumPMF(\n perTrialPMF,\n totalTrials,\n keepCount,\n node.mode === \"highest\",\n eps\n );\n }\n\n case \"d20Roll\": {\n const childDie = findDie(node.child);\n const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;\n return d20RollPMF(node.rollType, rerollOne);\n }\n\n case \"half\": {\n const childPMF = resolve(node.child, eps);\n return childPMF.scaleDamage(0.5, \"floor\");\n }\n\n case \"maxOf\": {\n const childPMF = resolve(node.child, eps);\n const count = Math.max(1, Math.floor(node.count));\n if (count === 1) return childPMF;\n\n // Compute the maximum of count independent rolls of childPMF\n return computeMaxOfPMF(childPMF, count, eps);\n }\n\n case \"scale\": {\n const childPMF = resolve(node.child, eps);\n const denom = node.denominator === 0 ? 1 : node.denominator;\n return childPMF.scaleDamage(node.numerator / denom, node.rounding);\n }\n }\n })();\n\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function pmfFromRollBuilder(\n rb: RollBuilder,\n eps: number = defaultEps\n): PMF {\n const ast = rb.toAST();\n return resolve(ast, eps);\n}\n\nfunction resolveSingleDie(die: DieNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(die);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = singleDiePMFCache.get(cacheKey);\n if (cached) return cached;\n\n const s = Math.max(0, Math.floor(die.sides));\n if (s <= 0) return PMF.delta(0, eps);\n\n let probs = new Map();\n for (let v = 1; v <= s; v++) probs.set(v, 1 / s);\n\n // TODO - check if this is correct. Sequential reroll passes? Or at once?\n const r = Math.max(0, Math.floor(die.reroll || 0));\n if (r > 0) {\n const k = Math.min(r, s);\n const rerollMass = k / s; // total probability rerolled once\n const uniformReroll = rerollMass / s; // mass added to each face from reroll\n const next = new Map();\n for (let v = 1; v <= s; v++) {\n const keep = v <= k ? 0 : 1 / s;\n next.set(v, keep + uniformReroll);\n }\n probs = next;\n }\n\n let pmf = PMF.fromMap(new Map(probs), eps);\n\n // Minimum per die\n const minV = Math.max(0, Math.floor(die.minimum || 0));\n if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV));\n\n // Exploding dice (finite) on max face only\n const explode = die.explode;\n if (explode && Number.isFinite(explode) && explode > 0) {\n const times = Math.floor(explode);\n const maxFace = s;\n\n // Split pmf into non-max and max\n const nonMax = new Map();\n const pMax = pmf.pAt(maxFace);\n for (const v of pmf.support()) {\n if (v !== maxFace) nonMax.set(v, pmf.pAt(v));\n }\n let nonMaxPMF = PMF.fromMap(nonMax, eps);\n if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) {\n // keep raw mass composition\n nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax);\n }\n\n // TODO - explosions\n // Additional roll distribution equals original pmf without explosions applied again.\n // For simplicity we treat cascaded explosions as adding uniform max-only triggers.\n // Compute sum of up to `times` additional rolls conditioned on each explosion hit.\n let tail = PMF.delta(0, eps);\n const addOnce = pmf;\n for (let t = 1; t <= times; t++) {\n tail = tail.convolve(addOnce, eps);\n }\n // Mix: with prob (1 - pMax) take nonMax, with prob pMax take maxFace + tail\n const exploded = PMF.branch(\n tail.mapDamage((v) => v + maxFace),\n nonMaxPMF,\n pMax\n );\n pmf = exploded;\n }\n\n singleDiePMFCache.set(cacheKey, pmf);\n return pmf;\n}\n\n// Getters\n\nfunction findDie(node: ExpressionNode): DieNode | undefined {\n switch (node.type) {\n case \"die\":\n return node;\n case \"constant\":\n return undefined;\n case \"sum\":\n case \"d20Roll\":\n case \"half\":\n case \"maxOf\":\n case \"scale\":\n return findDie(node.child);\n case \"keep\":\n return findDie(node.child.child);\n case \"add\":\n for (const c of node.children) {\n const d = findDie(c.node);\n if (d) return d;\n }\n return undefined;\n }\n}\n\nfunction getTotalCount(node: KeepNode): number {\n // The total dice count is encoded in the nearest SumNode under child\n let cur = node.child;\n while (cur.type === \"keep\") cur = cur.child;\n return cur.type === \"sum\" ? Math.max(0, Math.floor(cur.count)) : 0;\n}\n\nfunction computeMaxOfPMF(\n pmf: PMF,\n count: number,\n eps: number = defaultEps\n): PMF {\n // Compute the maximum of 'count' independent rolls of the given PMF\n if (count <= 1) return pmf;\n\n const support = pmf.support();\n const out = new Map();\n\n // For small counts, we can enumerate all outcomes\n if (count <= 6 && support.length <= 20) {\n function dfs(\n rollsLeft: number,\n currentMax: number,\n probability: number\n ): void {\n if (rollsLeft === 0) {\n out.set(currentMax, (out.get(currentMax) || 0) + probability);\n return;\n }\n\n for (const value of support) {\n const p = pmf.pAt(value);\n if (p > 0) {\n const newMax = Math.max(currentMax, value);\n dfs(rollsLeft - 1, newMax, probability * p);\n }\n }\n }\n\n dfs(count, -Infinity, 1);\n } else {\n // For larger cases, use the CDF method. Walk the sorted support once while\n // accumulating a running CDF, so each P(max = v) costs O(1) instead of a\n // full-map cdfAt() scan (previously O(N) per value → O(N²) overall).\n // Between two consecutive support points there is no probability mass, so\n // the running CDF up to (but not including) v equals cdfAt(v - 1).\n const sortedSupport = [...support].sort((a, b) => a - b);\n let runningCdf = 0;\n for (const value of sortedSupport) {\n const prevCdf = runningCdf;\n runningCdf += pmf.pAt(value);\n\n // P(max = value) = P(all rolls <= value) - P(all rolls <= value-1)\n const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count);\n if (probMax > eps) {\n out.set(value, probMax);\n }\n }\n }\n\n return PMF.fromMap(out, eps);\n}\n\nfunction keepSumPMF(\n single: PMF,\n total: number,\n keep: number,\n highest: boolean,\n eps: number = defaultEps\n): PMF {\n // Trivial/fast paths\n if (keep >= total) return single.power(total, eps);\n if (keep <= 0) return PMF.delta(0, eps);\n\n const sortedSupport = [...single.support()].sort((a, b) => a - b);\n const pmfSig = sortedSupport\n .map((val) => `${val}:${single.pAt(val).toPrecision(6)}`)\n .join(\",\");\n const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${\n highest ? 1 : 0\n }|e:${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n // kh1/kl1 fast paths using max-of machinery\n if (keep === 1) {\n if (highest) {\n return computeMaxOfPMF(single, total, eps);\n } else {\n // min of n i.i.d. == -max of n of negated variable\n const neg = single.mapDamage((v) => -v);\n const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v);\n builderPMFCache.set(cacheKey, minPMF);\n return minPMF;\n }\n }\n\n // DP over descending values; state = (used, remainingTrials) → map(sum -> prob)\n // Transition by drawing X occurrences at current value v from remainingTrials r: X ~ Binom(r, p)\n // Select t = min(X, keep - used) into the sum (highest picks first), then continue with r - X.\n\n type SumMap = Map;\n let state: Map = new Map();\n // Pack the (used, remainingTrials) state into a single integer key instead of\n // a \"used|r\" string. r ∈ [0, total], so a stride of (total + 1) is collision\n // free, and decoding is plain integer math — no split()/parseInt() per\n // transition in the hot loop. Behavior is identical (same states, same order).\n const stride = total + 1;\n const keyOf = (used: number, r: number) => used * stride + r;\n\n state.set(keyOf(0, total), new Map([[0, 1]]));\n\n const valuesDesc = highest\n ? [...sortedSupport].sort((a, b) => b - a)\n : [...sortedSupport].sort((a, b) => a - b);\n\n const binomPMF = (r: number, p: number): number[] => {\n if (r <= 0) return [1];\n if (p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[0] = 1;\n return arr;\n }\n if (1 - p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[r] = 1;\n return arr;\n }\n const q = 1 - p;\n const arr = new Array(r + 1).fill(0);\n\n // stable recurrence from k=0\n arr[0] = Math.pow(q, r);\n const ratio = p / q;\n for (let x = 1; x <= r; x++)\n arr[x] = ((arr[x - 1] * (r - x + 1)) / x) * ratio;\n\n // Normalize minor drift\n let s = 0;\n for (let x = 0; x <= r; x++) s += arr[x];\n if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s;\n\n return arr;\n };\n\n const pruneMap = (m: SumMap, threshold: number): SumMap => {\n if (threshold <= 0) return m;\n const out = new Map();\n for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr);\n return out.size === m.size ? m : out;\n };\n\n const pruneState = (st: Map, threshold: number) => {\n if (threshold <= 0) return st;\n const out = new Map();\n for (const [k, m] of st) {\n const mm = pruneMap(m, threshold);\n if (mm.size > 0) out.set(k, mm);\n }\n return out;\n };\n\n let processedMass = 0;\n for (const v of valuesDesc) {\n const p = single.pAt(v);\n if (p <= 0) continue;\n const q = Math.max(eps, 1 - processedMass);\n const pCond = Math.min(1, p / q);\n const next: Map = new Map();\n\n for (const [k, m] of state) {\n const used = Math.floor(k / stride);\n const r = k - used * stride;\n if (r === 0) {\n // No trials left; carry state forward unchanged\n const destKey = keyOf(used, 0);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr);\n next.set(destKey, dest);\n continue;\n }\n\n const bin = binomPMF(r, pCond);\n const remainingCapacity = keep - used;\n\n for (let x = 0; x <= r; x++) {\n const px = bin[x];\n if (px <= eps) continue;\n const t = Math.min(x, remainingCapacity);\n const used2 = used + t;\n const r2 = r - x;\n const add = t * v;\n\n const destKey = keyOf(used2, r2);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) {\n const s2 = sum + add;\n const prob = pr * px;\n const cur = dest.get(s2) || 0;\n const nv = cur + prob;\n if (nv >= eps) dest.set(s2, nv);\n }\n if (dest.size > 0) next.set(destKey, dest);\n }\n }\n\n // Light pruning proportional to eps\n state = pruneState(next, eps * 1e-6);\n processedMass += p;\n }\n\n // Collect results where all trials assigned and exactly keep were used\n const finalKey = keyOf(keep, 0);\n const dist = state.get(finalKey) ?? new Map();\n\n if (dist.size === 0) {\n // Fallback safety: return empty mass (should not happen)\n return PMF.emptyMass();\n }\n\n const result = PMF.fromMap(dist, eps);\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function getASTSignature(node: ExpressionNode): string {\n switch (node.type) {\n case \"constant\":\n return `c:${node.value}`;\n case \"die\": {\n // Use a fixed order for properties to ensure a stable signature.\n const parts: string[] = [];\n parts.push(`s:${node.sides}`);\n if (node.reroll) parts.push(`r:${node.reroll}`);\n if (node.minimum) parts.push(`m:${node.minimum}`);\n if (node.explode) parts.push(`e:${node.explode}`);\n return `d{${parts.join(\",\")}}`;\n }\n case \"sum\":\n return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"d20Roll\":\n return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`;\n case \"keep\":\n return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(\n node.child\n )}}`;\n case \"half\":\n return `half{ch:${getASTSignature(node.child)}}`;\n case \"maxOf\":\n return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"scale\":\n return `scale{n:${node.numerator},d:${node.denominator},r:${\n node.rounding\n },ch:${getASTSignature(node.child)}}`;\n case \"add\": {\n let constantValue = 0;\n const otherChildrenSigs: string[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n constantValue += c.sign * c.node.value;\n } else {\n otherChildrenSigs.push(\n `${c.sign === -1 ? \"-\" : \"+\"}${getASTSignature(c.node)}`\n );\n }\n }\n\n if (constantValue !== 0) {\n otherChildrenSigs.push(\n constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}`\n );\n }\n\n // Sort to handle commutative nature of addition.\n otherChildrenSigs.sort();\n\n return `add[${otherChildrenSigs.join(\"\")}]`;\n }\n }\n}\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport type { ACBuilder } from \"./ac\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport {\n AlwaysCritBuilder,\n AlwaysHitBuilder,\n ParsedRollBuilder,\n RollBuilder,\n} from \"./roll\";\nimport type { AttackResolution, CheckBuilder } from \"./types\";\n\ntype ActionEffect = RollBuilder;\n\nexport class AttackBuilder implements CheckBuilder {\n constructor(\n readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n private readonly hitEffect?: ActionEffect,\n private readonly critEffect?: ActionEffect | null,\n private readonly missEffect?: ActionEffect\n ) {}\n\n onCrit(val: number): AttackBuilder;\n onCrit(val: string): AttackBuilder;\n onCrit(val: RollBuilder): AttackBuilder;\n onCrit(count: number, die: RollBuilder): AttackBuilder;\n onCrit(count: number, sides: number): AttackBuilder;\n onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onCrit(count: number, sides: number, modifier: number): AttackBuilder;\n onCrit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n damageRoll,\n this.missEffect\n );\n }\n\n onMiss(val: number): AttackBuilder;\n onMiss(val: string): AttackBuilder;\n onMiss(val: RollBuilder): AttackBuilder;\n onMiss(count: number, die: RollBuilder): AttackBuilder;\n onMiss(count: number, sides: number): AttackBuilder;\n onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onMiss(count: number, sides: number, modifier: number): AttackBuilder;\n onMiss(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n this.critEffect,\n damageRoll\n );\n }\n\n noCrit(): AttackBuilder {\n return new AttackBuilder(this.check, this.hitEffect, null, this.missEffect);\n }\n\n // Legacy expressions\n toExpression(): string {\n const checkPart = this.check.toExpression();\n\n let effectPart = \"\";\n\n if (this.hitEffect) {\n effectPart = `(${this.hitEffect.toExpression()})`;\n if (this.critEffect !== null) {\n let crit: RollBuilder;\n if (this.critEffect) {\n crit = this.critEffect;\n } else {\n // For ParsedRollBuilder, we can't double dice, so skip the crit expression\n if (this.hitEffect instanceof ParsedRollBuilder) {\n // Don't try to double ParsedRollBuilder - leave it out of expression\n crit = RollBuilder.fromArgs(0);\n } else {\n crit =\n this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);\n }\n }\n\n const critThreshold = this.check.critThreshold;\n if (critThreshold < 1 || critThreshold > 20) {\n throw new Error(\n `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.`\n );\n }\n\n // Only include crit expression if crit is not zero\n const critExpression = crit.toExpression();\n if (critExpression !== \"0\") {\n if (critThreshold === 20) {\n effectPart += ` crit (${critExpression})`;\n } else {\n const xcritNumber = 21 - critThreshold;\n effectPart += ` xcrit${xcritNumber} (${critExpression})`;\n }\n }\n }\n\n if (this.missEffect) {\n effectPart += ` miss (${this.missEffect.toExpression()})`;\n }\n }\n\n return `${checkPart} * ${effectPart}`;\n }\n\n resolveProbabilities(\n check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n eps: number = 0\n ): { pSuccess: number; pHit: number; pCrit: number; pMiss: number } {\n const rollType = check.rollType;\n const rerollOne = check.baseReroll > 0;\n\n const critThreshold = check.critThreshold;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n if (check instanceof AlwaysCritBuilder) {\n // If fromAlwaysHit is true, everything is a crit (no misses)\n if (check.fromAlwaysHit) {\n return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };\n }\n\n // If fromAlwaysHit is false (came from ACBuilder), we need to check AC\n // Natural 1s always miss, everything else that would hit becomes a crit\n const ac = check.attackConfig.ac ?? 0;\n const staticMod = this.check.modifier;\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Natural 1 always misses\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Check if this roll would hit the AC\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n // Everything that hits becomes a crit\n pcrit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n return { pSuccess: pcrit, pHit: 0, pCrit: pcrit, pMiss: pmiss };\n }\n\n if (check instanceof AlwaysHitBuilder) {\n // Preserve rollType for crit odds\n let pCrit = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n if (r >= critThreshold) pCrit += pr;\n }\n const pHit = 1 - pCrit;\n const pMiss = 0;\n\n return { pSuccess: 1, pHit, pCrit, pMiss };\n }\n\n const ac = check.attackConfig.ac;\n const staticMod = this.check.modifier;\n\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let phit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Handle auto-miss\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Handle crit\n if (r >= critThreshold) {\n pcrit += pr;\n continue;\n }\n\n // Handle normal hit/miss\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n phit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n const psuccess = phit + pcrit;\n return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss };\n }\n\n resolve(eps: number = EPS): AttackResolution {\n const {\n pHit,\n pCrit,\n pMiss: pmiss,\n } = this.resolveProbabilities(this.check, eps);\n const hitPMF = this.hitEffect\n ? this.hitEffect instanceof ParsedRollBuilder\n ? this.hitEffect.toPMF(eps)\n : pmfFromRollBuilder(this.hitEffect, eps)\n : PMF.delta(0, eps);\n\n let critPMF: PMF | null = null;\n let phit = pHit;\n let pcrit = pCrit;\n\n if (this.critEffect === null) {\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n } else {\n let critBuilder: RollBuilder | undefined;\n \n if (this.critEffect) {\n critBuilder = this.critEffect;\n } else if (this.hitEffect instanceof ParsedRollBuilder) {\n // For ParsedRollBuilder, we can't automatically double dice\n // So treat it as noCrit() - roll crit probability into hit\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n critBuilder = undefined;\n } else {\n critBuilder = this.hitEffect?.copy().doubleDice();\n }\n\n if (critBuilder) {\n critPMF = critBuilder instanceof ParsedRollBuilder\n ? critBuilder.toPMF(eps)\n : pmfFromRollBuilder(critBuilder, eps);\n }\n }\n const missPMF = this.missEffect\n ? this.missEffect instanceof ParsedRollBuilder\n ? this.missEffect.toPMF(eps)\n : pmfFromRollBuilder(this.missEffect, eps)\n : PMF.delta(0, eps);\n\n // Mix them up\n const mix = new Mixture(eps);\n if (phit > 0) mix.add(\"hit\", hitPMF, phit);\n if (critPMF && pcrit > 0) mix.add(\"crit\", critPMF, pcrit);\n if (pmiss > 0)\n mix.add(this.missEffect ? \"missDamage\" : \"missNone\", missPMF, pmiss);\n\n return {\n pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps),\n check: this.check.toPMF(eps) ?? PMF.delta(0, eps),\n hit: hitPMF ?? PMF.delta(0, eps),\n crit: critPMF ?? PMF.delta(0, eps),\n miss: missPMF ?? PMF.delta(0, eps),\n weights: { hit: phit, crit: pcrit, miss: pmiss },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport { AlwaysCritBuilder, RollBuilder } from \"./roll\";\n\nexport interface AttackConfig {\n ac: number;\n critThreshold: number;\n}\nexport class ACBuilder extends RollBuilder {\n readonly attackConfig: AttackConfig;\n\n constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig) {\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig, ac };\n } else {\n this.attackConfig = { ac, critThreshold: 20 };\n }\n }\n\n // onHit(effect: RollBuilder): AttackBuilder {\n // return new AttackBuilder(this).onHit(effect)\n // }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(threshold: number): ACBuilder {\n const newConfig: AttackConfig = {\n ...this.attackConfig,\n critThreshold: threshold,\n };\n return new ACBuilder(this, this.attackConfig.ac, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(\n this,\n {\n critThreshold: this.attackConfig.critThreshold,\n ac: this.attackConfig.ac,\n },\n false\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs(); // This already includes bonus dice, no need to add them again\n const expression = new RollBuilder(configs).toExpression();\n return this.attackConfig.ac\n ? `(${expression} AC ${this.attackConfig.ac})`\n : expression;\n }\n\n override toPMF(eps: number = 0): PMF {\n const ac = this.attackConfig.ac;\n\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n const staticMod = this.modifier;\n const bonusPMFs = this.getBonusDicePMFs(this, eps);\n\n // Build total to-hit value distribution attackRollPMF = d20 ⊕ bonusDice, then shift by staticMod\n const parts = [d20, ...bonusPMFs];\n let attackRollPMF = parts.length === 1 ? d20 : PMF.convolveMany(parts, eps);\n if (staticMod !== 0)\n attackRollPMF = attackRollPMF.mapDamage(\n (rollValue) => rollValue + staticMod\n );\n\n // Map to 0 when below AC\n const out = new Map();\n for (const rollValue of attackRollPMF.support()) {\n const p = attackRollPMF.pAt(rollValue);\n const key = rollValue >= ac ? rollValue : 0;\n out.set(key, (out.get(key) || 0) + p);\n }\n return PMF.fromMap(out, eps);\n }\n\n override copy(): ACBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const newConfig = {\n ac: this.attackConfig.ac,\n critThreshold: this.attackConfig.critThreshold,\n };\n return new ACBuilder(baseCopy, newConfig.ac, newConfig);\n }\n}\n\n// Augment the RollBuilder prototype to implement the ac method\nRollBuilder.prototype.ac = function (targetAC: number): ACBuilder {\n if (isNaN(targetAC)) throw new Error(\"Invalid NaN value for targetAC\");\n return new ACBuilder(this, targetAC);\n};\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport type { DCBuilder } from \"./dc\";\nimport { ParsedRollBuilder, type RollBuilder } from \"./roll\";\nimport type { CheckBuilder, SaveResolution } from \"./types\";\n\nexport type SaveOutcome = \"normal\" | \"half\";\n\nexport class SaveBuilder implements CheckBuilder {\n constructor(\n readonly check: DCBuilder,\n private readonly failureEffect?: RollBuilder,\n private readonly saveOutcome: SaveOutcome = \"normal\"\n ) {}\n\n saveHalf(): SaveBuilder {\n return new SaveBuilder(this.check, this.failureEffect, \"half\");\n }\n\n toExpression(): string {\n const checkPart = this.check.toExpression();\n if (!this.failureEffect) return checkPart;\n\n const failureEffectPart = this.failureEffect.toExpression();\n const result = `${checkPart} * (${failureEffectPart})`;\n return this.saveOutcome === \"half\" ? `${result} save half` : result;\n }\n\n resolve(eps: number = EPS): SaveResolution {\n const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities(\n this.check\n );\n const failPMF = this.failureEffect\n ? this.failureEffect instanceof ParsedRollBuilder\n ? this.failureEffect.toPMF(eps)\n : pmfFromRollBuilder(this.failureEffect)\n : PMF.delta(0);\n const onSuccess = this.saveOutcome ?? \"half\";\n\n let successPMF: PMF = PMF.delta(0, eps);\n if (onSuccess === \"half\") successPMF = failPMF.scaleDamage(0.5, \"floor\");\n\n const successLabel: OutcomeType =\n onSuccess === \"normal\" ? \"missNone\" : \"saveHalf\";\n const failLabel: OutcomeType = \"saveFail\";\n const baseMix = new Mixture(eps);\n const mixture = baseMix\n .add(successLabel, successPMF, psuccess)\n .add(failLabel, failPMF, pfail);\n\n return {\n pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps),\n check:\n PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps),\n saveFail: failPMF ?? PMF.delta(0, eps),\n saveSuccess: successPMF ?? PMF.delta(0, eps),\n weights: { success: psuccess, fail: pfail },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n\nfunction resolveProbabilities(check: DCBuilder): {\n pSuccess: number;\n pFail: number;\n} {\n const saveBonus = check.modifier;\n const dc = check.saveDC;\n const d20Type = check.rollType;\n const baseReroll = check.baseReroll;\n // TODO later check if base reroll is not 0 or 1.\n\n const die = d20RollPMF(d20Type, baseReroll > 0);\n const faceP = new Map();\n for (const [r, bin] of die) {\n const pr = bin.p;\n if (pr > 0) faceP.set(r, pr);\n }\n\n // Now add bonus dice to the PMF (bless, bane, bardic, etc)\n const eps = 0;\n const bonusDicePMFs = check.getBonusDicePMFs(check, eps);\n const bonusPMF =\n bonusDicePMFs.length > 0\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.zero(eps);\n\n let pSuccess = 0;\n for (let r = 1; r <= 20; r++) {\n const pr = faceP.get(r);\n if (!pr) continue;\n const need = dc - saveBonus - r;\n pSuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pFail = Math.max(0, 1 - pSuccess);\n return { pSuccess, pFail: pFail };\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport { RollBuilder } from \"./roll\";\nimport { SaveBuilder } from \"./save\";\n\ninterface SaveConfig {\n dc: number;\n}\n\nexport class DCBuilder extends RollBuilder {\n private readonly saveConfig: SaveConfig;\n\n constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig) {\n super(baseRoll.getSubRollConfigs());\n this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 };\n }\n\n override dc(saveDC: number): DCBuilder {\n if (this.rollType && this.rollType === \"elven accuracy\") {\n throw new Error(\n \"Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead.\"\n );\n }\n return new DCBuilder(this, { dc: saveDC });\n }\n\n get saveDC(): number {\n return this.saveConfig.dc;\n }\n\n override add(anotherRoll: RollBuilder): DCBuilder {\n const newBuilder = super.add(anotherRoll);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n override addRoll(count?: number): DCBuilder {\n const newBuilder = super.addRoll(count);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n onSaveFailure(val: number): SaveBuilder;\n onSaveFailure(val: string): SaveBuilder;\n onSaveFailure(val: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, sides: number): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder;\n onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder;\n onSaveFailure(...args: any[]): SaveBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new SaveBuilder(this, damageRoll);\n }\n\n override withElvenAccuracy(): never {\n throw new Error(\n \"Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks).\"\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const subConfigs = this.getSubRollConfigs();\n const allConfigs = [...subConfigs];\n const expression = new RollBuilder(allConfigs).toExpression();\n return `(${expression} DC ${this.saveConfig.dc})`;\n }\n\n override toPMF(eps: number = 0): PMF {\n const saveDC = this.saveDC;\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n const staticMod = this.modifier;\n const bonusDicePMFs = this.getBonusDiceConfigs().map((cfg) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps)\n );\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let psuccess = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n const need = saveDC - staticMod - r;\n psuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pfail = Math.max(0, 1 - psuccess);\n const m = new Map([\n [0, psuccess > 0 ? psuccess : 0],\n [1, pfail > 0 ? pfail : 0],\n ]);\n return PMF.fromMap(m, eps);\n }\n}\n\n// Augment the RollBuilder prototype to implement the dc method\nRollBuilder.prototype.dc = function (saveDC: number): DCBuilder {\n if (isNaN(saveDC)) throw new Error(\"Invalid NaN value for saveDC\");\n return new DCBuilder(this).dc(saveDC);\n};\n"]} \ No newline at end of file diff --git a/dist/index.cjs b/dist/index.cjs new file mode 100644 index 0000000..372a2cf --- /dev/null +++ b/dist/index.cjs @@ -0,0 +1,3627 @@ +'use strict'; + +// src/common/bounce.ts +function binom(n, k) { + if (k < 0 || k > n) return 0; + let result = 1; + for (let i = 0; i < k; i++) result = result * (n - i) / (i + 1); + return result; +} +function pAllDistinct(dice, faces, uniformCount, heavyWeight) { + const light = 1 / faces; + const eK = binom(uniformCount, dice) * Math.pow(light, dice) + heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1); + let kFactorial = 1; + for (let i = 2; i <= dice; i++) kFactorial *= i; + return kFactorial * eK; +} +function pMatch(dice, faces, minimumDieRoll) { + if (dice <= 1) return 0; + if (dice > faces) return 1; + if (minimumDieRoll >= 2) { + const uniformCount = faces - minimumDieRoll; + const effectiveValues = uniformCount + 1; + if (dice > effectiveValues) return 1; + const heavyWeight = minimumDieRoll / faces; + const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight); + return Math.min(1, Math.max(0, 1 - distinct)); + } + let pDistinct = 1; + for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces; + return 1 - pDistinct; +} +function calculateBounceOdds(diceCount, dieFaces, options) { + if (diceCount <= 1) return 0; + if (diceCount > dieFaces) return 1; + const minimumDieRoll = options?.minimumDieRoll ?? 0; + const rerollDamageDice = options?.rerollDamageDice ?? 0; + const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll); + const rerollCount = Math.min(rerollDamageDice, diceCount); + if (rerollCount <= 0) return pMatchFirst; + const pNoMatchFirst = 1 - pMatchFirst; + const keptDice = diceCount - rerollCount; + const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces; + const pRerollDieMissesAll = keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1; + const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll; + const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0; + const pMatchAfterReroll = Math.min( + 1, + pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches) + ); + return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll); +} + +// src/common/errors.ts +var DiceParseError = class _DiceParseError extends Error { + constructor(message, options) { + super(message); + this.name = "DiceParseError"; + this.expression = options?.expression; + this.cause = options?.cause; + Object.setPrototypeOf(this, _DiceParseError.prototype); + } +}; + +// src/common/lru-cache.ts +var LRUCache = class { + constructor(maxSize = 1e3) { + this.maxSize = maxSize; + this.cache = /* @__PURE__ */ new Map(); + } + get(key) { + const value = this.cache.get(key); + if (value === void 0) return void 0; + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + delete(key) { + this.cache.delete(key); + } + set(key, value) { + if (this.cache.size >= this.maxSize && !this.cache.has(key)) { + const oldestKey = this.cache.keys().next().value; + this.cache.delete(oldestKey); + } + this.cache.delete(key); + this.cache.set(key, value); + return this; + } + clear() { + this.cache.clear(); + } + get size() { + return this.cache.size; + } + has(key) { + return this.cache.has(key); + } + keys() { + return this.cache.keys(); + } + values() { + return this.cache.values(); + } +}; + +// src/common/types.ts +var EPS = 1e-12; +function critProbability(critRange, rollType = "flat") { + const base = critRange / 20; + switch (rollType) { + case "advantage": + return 1 - (1 - base) ** 2; + case "elven accuracy": + return 1 - (1 - base) ** 3; + case "disadvantage": + return base ** 2; + case "flat": + default: + return base; + } +} +var MISS_NONE_OUTCOME = "missNone"; +var ALL_OUTCOME_TYPES = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" +]; +var OUTCOME_DISPLAY_ORDER = [ + "crit", + "hit", + "missDamage", + "saveHalf", + "saveFail", + "pc", + "missNone" +]; +function sortOutcomes(outcomes, order = ALL_OUTCOME_TYPES) { + const rank = new Map(order.map((o, i) => [o, i])); + return [...outcomes].sort((a, b) => { + const ra = rank.get(a); + const rb = rank.get(b); + if (ra !== void 0 && rb !== void 0) return ra - rb; + if (ra !== void 0) return -1; + if (rb !== void 0) return 1; + return a.localeCompare(b); + }); +} +var onAnyHit = ["hit", "crit"]; +var onCritOnly = ["crit"]; +var onHitOnly = ["hit"]; +var onMissOnly = ["missNone", "missDamage"]; +var onMissDamageOnly = ["missDamage"]; +var onSaveHalfOnly = ["saveHalf"]; +var onSaveFailOnly = ["saveFail"]; +var onPotentCantripOnly = ["pc"]; + +// src/pmf/query.ts +var _DiceQuery = class _DiceQuery { + constructor(singles, combined, eps = EPS) { + this.singles = Array.isArray(singles) ? singles : [singles]; + if (this.singles.some((s) => s === void 0)) { + throw new Error("DiceQuery contains undefined singles"); + } + this._eps = eps; + this._combinedProvided = combined !== void 0; + if (combined !== void 0) { + this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); + } + } + /** + * The combined damage distribution of all single PMFs (their convolution), + * normalized to total probability 1. + * + * Computed lazily on first access and cached. Queries that only need + * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, + * {@link DiceQuery.stddev} — never trigger this convolution. + */ + get combined() { + if (this._combined === void 0) { + const c = PMF.convolveMany(this.singles); + this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); + } + return this._combined; + } + /** + * Returns a new PMF with damage attribution metadata populated. + * + * This method computes attribution on-demand for builder-generated PMFs, + * enabling them to work with damage attribution charts. The `attr` field + * tracks how much damage each outcome type contributes at each damage value. + * + * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) + * + * Performance: Cached after first call. Adds minimal overhead vs `combined`. + * + * @returns PMF with attr field populated for damage attribution charts + * + * @example + * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) + * const query = attack.toQuery() + * const pmf = query.combinedWithAttribution() + * // Now pmf can be used with toDamageAttributionChartSeries() + */ + combinedWithAttribution() { + if (this._combinedWithAttr) { + return this._combinedWithAttr; + } + if (this.singles.every((pmf) => pmf.hasAttribution())) { + this._combinedWithAttr = this.combined; + return this._combinedWithAttr; + } + const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); + const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); + const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); + this._combinedWithAttr = normalized; + return normalized; + } + /** + * Per-label `damage value → probability mass` series for the combined, + * attribution-carrying distribution — the provenance core of the stacked + * damage-attribution chart. Convenience for + * `combinedWithAttribution().attributionByValue()`; see + * {@link PMF.attributionByValue}. + */ + attributionByValue() { + return this.combinedWithAttribution().attributionByValue(); + } + /** + * How many of the independent single PMFs can produce the given outcome + * label. Useful for "all of them succeeded" style probabilities where the + * exponent is the number of contributing attacks (see + * {@link DiceQuery.probExactlyK}). + */ + countSinglesWith(label) { + let count = 0; + for (const single of this.singles) { + if (single.hasOutcome(label)) count++; + } + return count; + } + /** + * Returns the expected damage across all possible outcomes. + * + * Example: `query.mean()` → 12.5 + * Use case: "What's my average damage per round?" + */ + mean() { + if (this._combinedProvided) { + let m = 0; + for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; + return m; + } + let totalMean = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; + } + return totalMean; + } + /** + * Returns the variance of the damage distribution. + * + * Example: `query.variance()` → 45.2 + * Use case: "How much does my damage vary from the average?" + * High variance means higher risk/reward. Lower variance means more consistent damage. + */ + variance() { + if (this._combinedProvided) { + const mu = this.mean(); + let v = 0; + for (const [damageValue, bin] of this.combined) { + const dev = damageValue - mu; + v += dev * dev * bin.p; + } + return v; + } + let totalVariance = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + if (Math.abs(mass - 1) <= this._eps) { + totalVariance += single.variance(); + } else { + let mu = 0; + for (const [d, b] of single) mu += d * (b.p / mass); + let v = 0; + for (const [d, b] of single) { + const dev = d - mu; + v += dev * dev * (b.p / mass); + } + totalVariance += v; + } + } + return totalVariance; + } + /** + * Returns the standard deviation of the damage distribution. + * + * Example: `query.stdev()` → 6.7 + * Use case: "What's the typical spread around my average damage?" + * Used to determine how consistent the damage is. + */ + stddev() { + return Math.sqrt(this.variance()); + } + /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ + stdev() { + return this.stddev(); + } + /** + * Returns the Cumulative Distribution Function. + */ + cdf(x) { + return this.probTotalAtMost(x); + } + /** + * Returns the probability of dealing X damage or less. + * In statistics, this is called the cumulative distribution function (CDF). + * Example: `query.cdf(20)` → 0.75 + * Use case: "What's the chance I deal 20 damage or less?" + */ + probTotalAtMost(x) { + let cumulativeProbability = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue <= x) { + cumulativeProbability += probabilityBin.p; + } + } + return cumulativeProbability; + } + /** + * Returns the Complementary Cumulative Distribution Function. + */ + ccdf(x) { + return this.probTotalAtLeast(x); + } + /** + * Returns the probability of dealing at least X damage. + * + * Example: `query.probTotalAtLeast(25)` → 0.35 + * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" + */ + probTotalAtLeast(threshold) { + let probabilitySum = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue >= threshold) { + probabilitySum += probabilityBin.p; + } + } + return probabilitySum; + } + /** + * Returns damage values at specific percentiles. + * + * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] + * Use case: "What are my 25th, 50th, and 75th percentile damage values?" + */ + percentiles(percentileValues) { + const sortedDamageValues = this.combined.support(); + if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); + const cumulativeProbabilities = []; + let runningProbabilitySum = 0; + for (const damageValue of sortedDamageValues) { + runningProbabilitySum += this.combined.map.get(damageValue).p; + cumulativeProbabilities.push(runningProbabilitySum); + } + return percentileValues.map((targetPercentile) => { + let leftBound = 0; + let rightBound = cumulativeProbabilities.length - 1; + while (leftBound <= rightBound) { + const middleIndex = Math.floor((leftBound + rightBound) / 2); + if (cumulativeProbabilities[middleIndex] >= targetPercentile) { + rightBound = middleIndex - 1; + } else { + leftBound = middleIndex + 1; + } + } + return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; + }); + } + /** + * Returns the minimum possible damage. + * + * Example: `query.min()` → 0 + * Use case: "What's the worst-case damage if everything misses?" + */ + min() { + return this.combined.min(); + } + /** + * Returns the maximum possible damage. + * + * Example: `query.max()` → 56 + * Use case: "What's the best-case damage if everything crits and rolls max?" + */ + max() { + return this.combined.max(); + } + singleProb(diceIndex, label) { + const single = this.singles[diceIndex]; + let probabilitySum = 0; + for (const [, probabilityBin] of single) { + probabilitySum += probabilityBin.count[label] || 0; + } + const mass = single.mass(); + return mass > 0 ? probabilitySum / mass : 0; + } + /** + * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it + * carries ANY of `labels`", over the n independent singles. + * + * Each single's per-event success probability is the Poisson-binomial + * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), + * computed exactly once. The binomial DP then runs once to produce the whole + * distribution, so the array-label paths of probExactlyK / probAtLeastK / + * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and + * re-running the DP per requested k. + */ + countDistribution(labels) { + const n = this.singles.length; + const successProbabilities = this.singles.map( + (single) => new _DiceQuery([single]).probabilityOf(labels) + ); + const dist = new Array(n + 1).fill(0); + dist[0] = 1; + for (const successProb of successProbabilities) { + for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { + dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; + } + dist[0] *= 1 - successProb; + } + return dist; + } + probAtLeastK(labels, k) { + const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; + const n = this.singles.length; + if (k <= 0) return 1; + if (k > n) return 0; + const dist = this.countDistribution(L); + let tail = 0; + for (let i = k; i <= n; i++) { + tail += dist[i]; + } + if (tail < 0) return 0; + if (tail > 1) return 1; + return tail; + } + /** + * Returns the probability that at least one attack has the specified outcome(s). + * - This is the complement of probAtMostK(labels, 0) + * + * Examples: + * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) + * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) + * + * Use cases: + * - "What's the chance at least one of my attacks connects?" + * + * Note: + * + * - You have to pass in an array of labels to avoid double-counting if you are + * using multiple labels. You cannot just add them. + */ + probAtLeastOne(labels) { + if (typeof labels === "string") { + labels = [labels]; + } + let productOfNonOccurrence = 1; + for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { + let combinedProbability = 0; + for (const label of labels) { + combinedProbability += this.singleProb(diceIndex, label); + } + if (combinedProbability < 0) combinedProbability = 0; + else if (combinedProbability > 1) combinedProbability = 1; + productOfNonOccurrence *= 1 - combinedProbability; + } + const result = 1 - productOfNonOccurrence; + return result < 0 ? 0 : result > 1 ? 1 : result; + } + /** + * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. + * + * Uses dynamic programming to efficiently calculate the probability distribution + * of how many attacks will have the specified outcome, accounting for different + * success probabilities across individual attacks. + * + * Example: For 3 attacks with 50% hit chance each, returns: + * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] + * + * @param label - The outcome type to count + * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) + * @returns Array where index K contains P(exactly K attacks have the label) + */ + computeBinomialProbabilities(label, maxK) { + const individualProbabilities = this.singles.map( + (_, diceIndex) => this.singleProb(diceIndex, label) + ); + const binomialProbs = new Array(maxK + 1).fill(0); + binomialProbs[0] = 1; + for (const singleProbability of individualProbabilities) { + for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { + binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; + } + binomialProbs[0] *= 1 - singleProbability; + } + return binomialProbs; + } + /** + * Returns the probability that exactly K attacks result in the specified outcome(s). + * + * Single label examples: + * - probExactlyK('hit', 2) = probability exactly 2 attacks hit + * - probExactlyK('crit', 1) = probability exactly 1 attack crits + * - probExactlyK('crit', 0) = probability no attacks crit + * + * Array examples: + * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed + * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds + * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss + * + * Use cases: + * - "What's the chance exactly one of my attacks hits?" + * - "How likely am I to get exactly 2 successes out of 3 attacks?" + * - "What's the probability that exactly half my attacks succeed?" + * + * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. + * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. + */ + probExactlyK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + return probabilityArray[k]; + } + const dist = this.countDistribution(labels); + return k >= 0 && k < dist.length ? dist[k] : 0; + } + /** + * Returns the probability that AT MOST K attacks result in the specified outcome(s). + * + * Single label examples: + * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) + * - probAtMostK('crit', 0) = probability no attacks crit + * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss + * + * Array examples: + * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds + * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) + * + * Use cases: + * - "What's the chance that at most one attack hits?" (rest miss) + * - "How likely am I to have mostly failures?" (at most 1 success) + * - "What's the probability of a really bad turn?" (at most 0 successes) + * + */ + probAtMostK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + let cumulativeSum2 = 0; + for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { + cumulativeSum2 += probabilityArray[outcomeCount]; + } + return cumulativeSum2; + } + const dist = this.countDistribution(labels); + const upper = Math.min(k, dist.length - 1); + let cumulativeSum = 0; + for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { + cumulativeSum += dist[outcomeCount]; + } + return cumulativeSum; + } + /** + * Returns the expected damage attributed to specific outcome types. + * + * Single label examples: + * - expectedDamageFrom('hit') = expected damage from hit components + * - expectedDamageFrom('crit') = expected damage from crit components + * + * Array examples: + * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success + * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses + * + * Use cases: + * - "How much damage do I expect from successful attacks?" + * - "What's the damage contribution from critical hits specifically?" + * - "How much damage comes from miss effects (like save-for-half spells)?" + */ + expectedDamageFrom(labels) { + const wanted = Array.isArray(labels) ? labels : [labels]; + let total = 0; + for (const single of this.singles) { + for (const [dmg, bin] of single) { + let p = 0; + for (const label of wanted) p += bin.count[label] ?? 0; + total += dmg * p; + } + } + return total; + } + /** + * Returns damage statistics for scenarios where AT LEAST ONE attack results in + * the specified outcome(s). + * + * This method answers "What happens when things go reasonably well?" rather than + * "What's the theoretical maximum?" It includes mixed scenarios which are more + * common and tactically relevant than pure scenarios. + * + * Single label examples: + * - damageStatsFrom('hit') = damage range when at least one attack hits + * - damageStatsFrom('crit') = damage range when at least one attack crits + * + * Array examples: + * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds + * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses + * + * Tactical Use Cases: + * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" + * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" + * - "Should I use this risky spell if it has good damage when it works?" + * - "What's my damage potential when something goes right?" (vs pure failure) + * + * Combat Planning Examples: + * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" + * (Much more useful than "You average 50 damage including complete misses") + * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" + * - Resource management: "If I hit anything, I'll likely finish this enemy" + * + * Statistical Note: + * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which + * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. + * + * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an + * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and + * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than + * E[dmg | the label occurs]. For a single attack both are the plain + * conditional figures. Use {@link probAtLeastOne} for the scenario probability. + * + * @example + * // High-level tactical planning + * const successStats = query.damageStatsFrom('hit') + * const successChance = query.probAtLeastOne('hit') + * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) + */ + damageStatsFrom(labels) { + const labelArray = typeof labels === "string" ? [labels] : labels; + let minDamage = Infinity; + let maxDamage = -Infinity; + let totalDamage = 0; + let totalCount = 0; + for (const [damage, probabilityBin] of this.combined) { + let binHasAnyLabel = false; + let binContribution = 0; + for (const label of labelArray) { + const count = probabilityBin.count[label]; + if (count && count > 0) { + binHasAnyLabel = true; + binContribution += count; + } + } + if (damage > 0 && binHasAnyLabel) { + minDamage = Math.min(minDamage, damage); + maxDamage = Math.max(maxDamage, damage); + const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; + totalDamage += damage * weightToUse; + totalCount += weightToUse; + } + } + return { + min: minDamage === Infinity ? 0 : minDamage, + max: maxDamage === -Infinity ? 0 : maxDamage, + avg: totalCount > 0 ? totalDamage / totalCount : 0, + count: totalCount + }; + } + /** + * Returns damage statistics for scenarios where ALL attacks result in the specified + * outcome, calculated by leveraging the pure partition of singles. + * + * This method answers "What's the theoretical best/worst case?" and "What are the + * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. + * + * Examples: + * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) + * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) + * + * UI and Display Use Cases: + * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) + * - "Best case scenario" vs "worst case scenario" analysis + * - Mathematical verification: "Does our hit damage calculation match manual math?" + * - Clean damage type attribution: "How much comes from base hits vs crits?" + * + * Design and Balance Use Cases: + * - Game designers: "What's the damage ceiling if someone gets lucky?" + * - Character optimization: "What's my absolute maximum potential?" + * - Ability comparison: "Which build has higher crit ceiling?" + * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" + * + * Mathematical Use Cases: + * - Validating complex calculations against simple manual math + * - Understanding damage component contributions in isolation + * - Separating luck (crit variance) from consistency (hit variance) + * - Building intuition about damage sources + * + * When to Use This vs damageStatsFrom(): + * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons + * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios + * + * Statistical Note: + * Pure scenarios (all hits, all crits) are rare but represent clear mathematical + * boundaries. These stats help understand the "shape" of your damage potential. + * + * @example + * // UI display logic + * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" + * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" + * + * // vs tactical planning (use damageStatsFrom instead) + * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios + */ + combinedDamageStats(targetLabel) { + const singleStats = this.singles.map( + (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) + ); + if (singleStats.some((stats) => stats.count === 0)) { + return { min: 0, max: 0, avg: 0, count: 0 }; + } + const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); + const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); + const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); + const combinedProb = singleStats.reduce( + (product, stats) => product * stats.count, + 1 + ); + return { + min: combinedMin, + max: combinedMax, + avg: combinedAvg, + count: combinedProb + }; + } + /** + * Returns the probability that at least one attack carries ANY of the + * specified labels (the marginal P(≥1) across the independent attacks). + * + * Examples: + * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) + * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) + * + * Use cases: + * - "What's the chance my resolution includes a success label?" + * - "How likely am I to get any hits or crits across all attacks?" + * + * Note: this must NOT be computed by summing `combined` bin probabilities. A + * single combined damage total is reachable by many outcome combinations and + * a bin can hold several labels at once, so summing `bin.p` over bins that + * contain a label over-counts. The correct marginal is the Poisson-binomial + * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. + */ + probabilityOf(labels) { + return this.probAtLeastOne(labels); + } + /** + * Returns the probability of missing (any type of miss). + * + * Example: `query.missChance()` → 0.04 + * Use case: "What's the chance I miss completely this turn?" + */ + missChance() { + return this.probabilityOf(["missDamage", "missNone"]); + } + /** + * Returns data formatted for plotting damage probability distribution. + * + * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] + * Use case: "I want to visualize my damage distribution in a chart." + */ + toChartSeries() { + return this.combined.support().map((damageValue) => ({ + x: damageValue, + y: this.combined.map.get(damageValue).p + })); + } + /** + * Returns tabular data showing damage values and their probability breakdowns. + * + * Example: `query.toLabeledTable(['hit', 'crit'])` → + * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] + * + * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." + */ + toLabeledTable(labels = []) { + return this.combined.support().map((damageValue) => { + const probabilityBin = this.combined.map.get(damageValue); + const tableRow = { + damage: damageValue, + total: probabilityBin.p + }; + for (const outcomeLabel of labels) { + tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; + } + return tableRow; + }); + } + /** + * Returns data for stacked charts with unconditional per-label probability mass per damage. + * + * - Each dataset value equals the unconditional probability mass for that label at that damage + * (i.e., `bin.count[label]`). + * - Column sums may be less than the total probability `bin.p` when you omit labels or when + * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. + * - This behavior matches tests that expect raw per-label mass (not proportional scaling). + * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. + * + * @example + * query.toStackedChartData(['hit', 'crit']) + * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} + */ + toStackedChartData(labels = [], epsilon = EPS) { + const damageValues = this.combined.support(); + const datasets = labels.map((outcomeLabel) => ({ + label: outcomeLabel, + data: damageValues.map((dmg) => { + const bin = this.combined.map.get(dmg); + const v = bin ? bin.count[outcomeLabel] || 0 : 0; + return v <= epsilon ? 0 : v; + }) + })); + return { labels: damageValues, datasets }; + } + /** + * Returns pure mathematical data for attribution charts showing outcome contributions. + * + * Automatically discovers all outcome types present in the PMF, applies filtering rules, + * and returns proportional data suitable for stacked visualization. + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and proportional data + * + * @example + * query.toAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} + */ + toAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeCount = bin.count[outcome] || 0; + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + const outcomeProbability = bin.p * outcomeFraction; + return asPercentages ? outcomeProbability * 100 : outcomeProbability; + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for damage attribution charts showing damage contribution + * from each outcome type at each damage value. + * + * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of + * bin.count (probability attribution). + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) + * @returns Pure data structure with support, outcomes, and damage attribution data + * + * @example + * query.toDamageAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} + */ + toDamageAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + if (bin.attr) { + for (const outcomeType in bin.attr) { + if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin || !bin.attr) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeDamageAttribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { + if (filterRules(outcomeName, damage)) { + totalDamageAttribution += damageAttr || 0; + } + } + if (totalDamageAttribution === 0) return 0; + const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; + return damagePercentage * bin.p * 100; + } else { + return outcomeDamageAttribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for outcome attribution charts showing which + * attack outcome combinations can produce each damage value. + * + * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks + * outcome combinations - answering "what attack outcomes produced this damage?" + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and outcome combination probabilities + * + * @example + * query.toOutcomeAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} + */ + toOutcomeAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + if (outcome === "missNone") { + const outcomeCount = bin.count[outcome] || 0; + if (outcomeCount === 0) return 0; + if (asPercentages) { + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + return outcomeFraction * bin.p * 100; + } else { + return outcomeCount; + } + } + if (!bin.attr) return 0; + const outcomeDamageContribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [, damageAttr] of Object.entries(bin.attr)) { + totalDamageAttribution += damageAttr || 0; + } + if (totalDamageAttribution === 0) return 0; + const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; + return outcomeFraction * bin.p * 100; + } else { + return outcomeDamageContribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for cumulative distribution function (CDF). + * Shows P(X ≤ x) - the probability of getting at most x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and cumulative probabilities + * + * @example + * query.toCDFSeries() + * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} + */ + toCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const cdfData = []; + for (const damage of support) { + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + cdfData.push( + asPercentages ? cumulativeProbability * 100 : cumulativeProbability + ); + } + return { + support, + data: cdfData + }; + } + /** + * Returns pure mathematical data for complementary cumulative distribution function (CCDF). + * Shows P(X ≥ x) - the probability of getting at least x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and complementary cumulative probabilities + * + * @example + * query.toCCDFSeries() + * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} + */ + toCCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const ccdfData = []; + for (const damage of support) { + const ccdf = 1 - cumulativeProbability; + ccdfData.push(asPercentages ? ccdf * 100 : ccdf); + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + } + return { + support, + data: ccdfData + }; + } + /* + Statistics snapshot of the query. + */ + /** Probability of doing strictly more than threshold damage (default >0). */ + probDamageGreaterThan(threshold = 0) { + let acc = 0; + for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; + return acc; + } + /** All outcome keys actually present (typed & ordered if you pass an order). */ + outcomeKeys(order) { + const found = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) + if (bin.count[k] && bin.count[k] > 0) found.add(k); + } + if (found.size === 0) + ["hit", "crit", "missNone"].forEach((k) => found.add(k)); + const keys = Array.from(found).filter( + (k) => order?.includes(k) ?? true + ); + if (order && order.length) + keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); + return keys; + } + /** Total probability per outcome across the PMF. */ + outcomeTotals(outcomes = this.outcomeKeys()) { + const totals = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => totals.set(o, 0)); + for (const [, row] of this.combined.map) { + for (const o of outcomes) { + const p = row.count[o] || 0; + totals.set(o, (totals.get(o) || 0) + p); + } + } + return totals; + } + /** Conditional damage range per outcome (min/avg/max of X | outcome). */ + outcomeDamageRanges(outcomes = this.outcomeKeys()) { + const table = this.toLabeledTable(outcomes); + const ranges = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); + for (const row of table) { + const dmg = row.damage; + for (const o of outcomes) { + const p = row[o] || 0; + if (p > 0) { + const r = ranges.get(o); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + } + const out = /* @__PURE__ */ new Map(); + for (const o of outcomes) { + const r = ranges.get(o); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); + } + return out; + } + /** + * Snapshot of the distribution in the exact shape the UI consumes. + * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) + * - damageRange is conditional on the outcome occurring + * + * The outcome probabilities use the correct Poisson-binomial marginals + * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), + * so they are always valid probabilities in [0,1]. + * + * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from + * the combined PMF's `count`, which the convolution accumulates as an EXPECTED + * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] + * rather than a clean conditional expectation. It is correct for a single + * attack. + */ + snapshot(order) { + const discovered = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) { + if (bin.count[k] && bin.count[k] > 0) discovered.add(k); + } + } + if (discovered.size === 0) { + for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); + } + let outcomes = Array.from(discovered); + if (order && order.length) { + const inOrder = new Set(order); + outcomes = outcomes.filter((k) => inOrder.has(k)); + const rank = new Map(order.map((k, i) => [k, i])); + outcomes.sort( + (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) + ); + } + const rows = this.toLabeledTable(outcomes); + const rangeAcc = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + rangeAcc.set(ot, { sum: 0, mass: 0 }); + } + for (const row of rows) { + const dmg = row.damage; + for (const ot of outcomes) { + const p = row[ot] || 0; + if (p <= 0) continue; + const r = rangeAcc.get(ot); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + const n = this.singles.length; + const outcomeMap = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + const r = rangeAcc.get(ot); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + outcomeMap.set(ot, { + atLeastOneProbability: this.probAtLeastOne(ot), + allProbability: this.probAtLeastK(ot, n), + damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } + }); + } + const averageDPR = this.mean(); + let damageChance = 0; + for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; + const { support, data } = this.toCDFSeries(false); + const quantile = (p) => { + if (support.length === 0) return 0; + for (let i = 0; i < support.length; i++) + if (data[i] >= p) return support[i]; + return support[support.length - 1]; + }; + const percentiles = { + p25: quantile(0.25), + p50: quantile(0.5), + p75: quantile(0.75) + }; + return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; + } + /** + * PMF Transformation Methods + * + * These methods provide a fluent API for transforming dice queries by wrapping + * the underlying PMF transformation methods. All operations work on the combined + * PMF and return new DiceQuery instances. + */ + /** + * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). + * + * @returns New DiceQuery with normalized combined PMF + */ + normalize() { + return new _DiceQuery([this.combined.normalize()]); + } + /** + * Returns a new DiceQuery with low-probability outcomes removed. + * + * @param eps Minimum probability threshold (defaults to PMF epsilon) + * @param keepFinalBin Whether to keep the highest damage bin regardless of probability + * @returns New DiceQuery with compacted combined PMF + */ + compact(eps, keepFinalBin) { + return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); + } + /** + * Returns a new DiceQuery with an additional scaled branch added. + * Useful for conditional outcomes like "30% chance of opportunity attack". + * + * @param branch DiceQuery to add as a scaled branch + * @param probability Probability of the branch occurring (0-1) + * @returns New DiceQuery combining this query with the scaled branch + * + * @example + * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); + * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); + */ + addScaled(branch, probability) { + return new _DiceQuery([ + this.combined.addScaled(branch.combined, probability) + ]); + } + /** + * Returns a new DiceQuery with all probabilities scaled by a factor. + * Used for conditional scenarios where the entire outcome has reduced probability. + * + * @param factor Scaling factor for probabilities + * @returns New DiceQuery with scaled probabilities + * + * @example + * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario + */ + scaleMass(factor) { + return new _DiceQuery([this.combined.scaleMass(factor)]); + } + totalMass() { + return this.combined.mass(); + } + /** + * Returns a new DiceQuery with damage values transformed by a function. + * Useful for applying modifiers, resistances, or other damage transformations. + * + * @param damageTransformFunction Function to transform each damage value + * @returns New DiceQuery with transformed damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage + * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage + */ + mapDamage(damageTransformFunction) { + return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); + } + /** + * Returns a new DiceQuery with damage values scaled by a factor. + * Convenient wrapper around mapDamage for multiplicative scaling. + * + * @param factor Scaling factor for damage values + * @param rounding Rounding method: "floor" (default), "round", or "ceil" + * @returns New DiceQuery with scaled damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const doubled = baseAttack.scaleDamage(2); // Double damage + * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded + */ + scaleDamage(factor, rounding = "floor") { + return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); + } + /** + * Returns a new DiceQuery combining this query with another via convolution. + * Equivalent to rolling both queries independently and adding results. + * It is important to use this rather than combing()ing the PMFs directly! + * This method maintains the provenance of the PMFs which is needed for damage attribution. + * Combining the .combined PMFs directly is still valid for DPR calculations but + * is not statistically sound for queries. + * + * @param other DiceQuery to combine with + * @param eps Optional epsilon for precision control + * @returns New DiceQuery representing the combined outcome + * + * @example + * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); + * const bothAttacks = mainAttack.convolve(bonusAttack); + */ + convolve(other) { + const singles = [...this.singles, ...other.singles]; + return new _DiceQuery(singles); + } + /** + * First-success split over an ordered list of DISTINCT single-swing PMFs. + * Each PMF may have different success/subset probabilities (from labels). + * + * successOutcome: e.g., ["success"] or ["hit", "crit"] + * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success + * + * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] + */ + firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { + const pmfs = this.singles; + if (!pmfs.length) { + throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); + } + const toArr = (x) => Array.isArray(x) ? x : [x]; + const clamp01 = (x) => Math.max(0, Math.min(1, x)); + const tol = Math.max(eps, 8 * Number.EPSILON); + const per = pmfs.map((pmf) => { + const dq = new _DiceQuery([pmf]); + const pS = dq.probAtLeastOne(toArr(successOutcome)); + const pB = dq.probAtLeastOne(toArr(subsetOutcome)); + if (pB - pS > eps) { + throw new Error( + "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." + ); + } + return { pS, pB }; + }); + let missSoFar = 1; + let pFirstSubset = 0; + let pFirstNonSubset = 0; + let pNone = 1; + for (const { pS, pB } of per) { + pFirstSubset += missSoFar * pB; + pFirstNonSubset += missSoFar * (pS - pB); + const miss = 1 - pS; + missSoFar *= miss; + pNone *= miss; + } + const pAny = 1 - pNone; + const a = clamp01(pFirstNonSubset); + const b = clamp01(pFirstSubset); + const any = clamp01(pAny); + const none = clamp01(pNone); + if (Math.abs(a + b - any) > tol * Math.max(1, any)) { + throw new Error( + `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` + ); + } + return [a, b, any, none]; + } +}; +_DiceQuery.DEFAULT_OUTCOMES = [ + "hit", + "crit", + "missNone" +]; +var DiceQuery = _DiceQuery; +var pmfCache = new LRUCache(1e3); +var _PMF = class _PMF { + constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { + this.map = map; + this.epsilon = epsilon; + this.normalized = normalized; + this.identifier = identifier; + this._preservedProvenance = _preservedProvenance; + } + static empty(epsilon = EPS, identifier = "empty") { + return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); + } + // This has a single bin at value 0, mass of 1 + static zero(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "zero"); + } + static delta(value, epsilon = EPS) { + return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); + } + /** + * Point mass at damage 0 tagged with the canonical `missNone` outcome. + * + * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the + * builder's attack-resolution vocabulary. This uses the `missNone` + * {@link OutcomeType} that the attribution charts and outcome stats key on, + * so it is the correct "clean miss / no damage" delta for provenance-aware + * mixtures feeding those consumers. + */ + static missNone(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "missNone"); + } + // This creates a single bin at value 0, but with weight 0. + static emptyMass() { + return _PMF.zero().scaleMass(0); + } + // Makes PMF iterable over [damage, bin] pairs. + [Symbol.iterator]() { + return this.map[Symbol.iterator](); + } + static clearCache() { + pmfCache.clear(); + } + /** + * Creates a conditional PMF from two branches (success and failure) and a probability. + * This is the core logic for modeling any probabilistic event where there are two + * distinct outcomes. + */ + static branch(successPMF, failurePMF, successProbability) { + let p = successProbability; + if (!Number.isFinite(p)) p = 0; + if (p < 0) p = 0; + if (p > 1) p = 1; + const q = 1 - p; + if (p === 0) return failurePMF.scaleMass(1); + if (p === 1) return successPMF.scaleMass(1); + const eps = successPMF.epsilon ?? failurePMF.epsilon; + const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; + const resultMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of failurePMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); + } + for (const [damageValue, bin] of successPMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); + } + return new _PMF(resultMap, eps, false, id); + } + /** + * withProbability() + * + * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). + * + * Think of this as a shortcut for: + * pmf.gate(p, PMF.zero()) + * + * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, + * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. + * + * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. + * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. + * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) + * use query.firstSuccessSplit() to get the exact probabilities. + * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). + * + */ + static withProbability(successPMF, probability) { + return _PMF.branch(successPMF, _PMF.zero(), probability); + } + /** + * gate() + * + * A conditional wrapper around branch() that applies this PMF with probability `p`, + * and applies a provided fallback PMF otherwise. + * + * This is useful for modeling a binary choice between two outcomes: + * - The "success" outcome (this PMF) happens with probability `p`. + * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. + * + * Examples: + * - 25% chance to include an opportunity attack, otherwise nothing: + * attackPMF.gate(0.25, PMF.zero()) + * + * - 50% chance to deal fireball damage, otherwise cone of cold damage: + * fireballPMF.gate(0.5, coneOfColdPMF) + * + * Relationship to other helpers: + * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. + * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). + * + * @param p Probability of applying this PMF (between 0 and 1). + * @param fallback PMF to apply when this PMF is *not* selected. + * @returns A new PMF representing the weighted mixture of this PMF and the fallback. + */ + gate(p, fallback) { + return _PMF.branch(this, fallback, p); + } + /** + * PMF.exclusive() + * + * Builds a single PMF from a set of mutually exclusive weighted outcomes. + * Exactly one of the provided options will occur. + * + * Each option has: + * - A PMF representing its outcome (e.g., damage dice). + * - A weight representing its probability of being selected. + * + * Notes: + * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for floating point rounding. + */ + static exclusive(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight) || weight < -eps) { + throw new Error(`PMF.exclusive: invalid weight ${weight}.`); + } + } + let totalWeight = items.reduce((s, { weight }) => s + weight, 0); + if (Math.abs(totalWeight) <= eps) totalWeight = 0; + if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; + if (totalWeight > 1 + EPS) { + throw new Error( + `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` + ); + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (weight > eps) out = out.addScaled(pmf, weight); + } + const leftover = Math.max(0, 1 - totalWeight); + if (leftover > eps) { + out = out.addScaled(_PMF.zero(), leftover); + } + return out; + } + /** + * PMF.mix() + * + * Builds a PMF as a linear combination of input PMFs with the given weights. + * Unlike `exclusive`, this does NOT: + * - enforce that weights sum to 1 + * - add leftover probability to δ0 (PMF.zero()) + * + * Use when outcomes are not mutually exclusive, or for interpolation/blending. + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for skipping tiny weights. + */ + static mix(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight)) { + throw new Error(`PMF.mix: invalid weight ${weight}.`); + } + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (Math.abs(weight) <= eps) continue; + out = out.addScaled(pmf, weight); + } + return out; + } + /** + * Adds damage attribution metadata to this PMF based on existing count metadata. + * For each bin, sets attr[outcome] = damage × count[outcome]. + * + * This enables damage attribution charts to work with builder-generated PMFs. + * The parser generates attr automatically, but builder PMFs only have count. + * + * @returns New PMF with attr field populated in each bin + */ + /** + * Returns true if this PMF already carries damage attribution metadata. + * + * Only the first positive-damage bin is inspected (parser-generated PMFs + * populate `attr` uniformly), so this is O(1) in practice. + */ + hasAttribution() { + for (const [damage, bin] of this.map) { + if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { + return true; + } + if (damage > 0) break; + } + return false; + } + withAttribution() { + if (this.hasAttribution()) return this; + const newMap = /* @__PURE__ */ new Map(); + for (const [damage, bin] of this.map) { + const attr = {}; + for (const outcome in bin.count) { + const probability = bin.count[outcome]; + if (probability > 0) { + attr[outcome] = damage * probability; + } + } + newMap.set(damage, { + p: bin.p, + count: { ...bin.count }, + attr: Object.keys(attr).length > 0 ? attr : void 0 + }); + } + return new _PMF( + newMap, + this.epsilon, + this.normalized, + `${this.identifier}~attr` + ); + } + /** + * General-purpose N-way mixture. + * weights: Array of [weight, PMF]. + * + * Example: PMF.mixN([ + * [pMiss, zero], + * [pHit, hitPMF], + * [pCrit, critPMF], + * ]); + */ + static mixN(weights, eps = EPS) { + const filtered = weights.filter(([w]) => w > eps); + if (filtered.length === 0) { + return _PMF.emptyMass(); + } + let acc = null; + let sum = 0; + for (const [w, pmf] of filtered) { + if (acc === null) { + acc = pmf; + sum = w; + } else { + const q = w / (sum + w); + acc = _PMF.branch(pmf, acc, q); + sum += w; + } + } + return acc ?? _PMF.emptyMass(); + } + // This is a convenience method for when we use power + // TODO: It can be smarter in the future, and we can also add it to query + // That way statistics operations on invalid PMFs can throw an error + // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? + setPreservedProvenance(preserved) { + if (!this._preservedProvenance && preserved) { + throw new Error( + "Preserved provenance is already set to false, cannot fix that" + ); + } + this._preservedProvenance = preserved; + } + preservedProvenance() { + return this._preservedProvenance; + } + getPowerCacheKey(n, eps) { + const id = this.identifier; + let key = `${id}`; + for (let i = 1; i < n; i++) key += `+${id}`; + return `${key}@${eps}`; + } + /** + * Efficiently computes this PMF convolved with itself `n` times. + * Uses exponentiation by squaring to reduce total convolutions. + * n must be a positive integer. + * * + * * NOTE: This folds multiple independent attacks into a single PMF. + * As a result, The power() method causes a loss of data provenance. + * This is ONLY SAFE if you are trying to calculate masses. + * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). + */ + power(n, eps = this.epsilon) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("power(n): n must be a positive integer"); + } + if (n === 1) return this; + const epsilon = eps ?? this.epsilon; + const key = this.getPowerCacheKey(n, epsilon); + { + const cached = pmfCache?.get(key); + if (cached) return cached; + } + let base = this.normalized ? this : this.normalize(); + let result = base; + let exp = n - 1; + while (exp > 0) { + if (exp & 1) { + result = result.convolve(base, epsilon); + } + exp >>= 1; + if (exp > 0) { + base = base.convolve(base, epsilon); + } + } + result.setPreservedProvenance(false); + { + pmfCache?.set(key, result); + } + return result; + } + /* + * Helper for chaining multiple identical attacks + */ + replicate(n) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("replicate(n): n must be a positive integer"); + } + if (n === 1) return [this]; + return Array.from({ length: n }, () => this); + } + mass() { + if (this._totalMass === void 0) { + let totalProbabilityMass = 0; + for (const { p } of this.map.values()) { + totalProbabilityMass += p; + } + this._totalMass = totalProbabilityMass; + } + return this._totalMass; + } + outcomeMass(outcome) { + let totalProbabilityMass = 0; + for (const { p, count } of this.map.values()) { + totalProbabilityMass += p * (count[outcome] ?? 0); + } + return totalProbabilityMass; + } + // Helper for testing + faceTotal() { + return [...this.map.keys()].reduce((sum, key) => sum + key, 0); + } + normalize() { + if (this.normalized) return this; + const normalizationFactor = this.mass(); + if (normalizationFactor === 0) return this; + const normalizedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const normalizedCount = {}; + for (const labelKey in probabilityBin.count) { + normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; + } + let normalizedAttributes; + if (probabilityBin.attr) { + normalizedAttributes = {}; + for (const labelKey in probabilityBin.attr) { + normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; + } + } + normalizedMap.set(damageValue, { + p: probabilityBin.p / normalizationFactor, + count: normalizedCount, + attr: normalizedAttributes + }); + } + return new _PMF(normalizedMap, this.epsilon, true, this.identifier); + } + /** + * Returns a copy with negligible probabilities removed (p < eps). + * If keepFinalBin is true, the bin with the largest key is always kept, + * even if its probability is below eps. count/attr submaps are still cleaned. + */ + compact(eps = this.epsilon, keepFinalBin = false) { + let maxKey = -Infinity; + if (keepFinalBin) { + for (const key of this.map.keys()) { + if (key > maxKey) maxKey = key; + } + } + const compactedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; + if (!shouldKeep) continue; + const cleanedBin = _PMF.cloneBin(probabilityBin); + for (const labelKey in cleanedBin.count) { + if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { + delete cleanedBin.count[labelKey]; + } + } + if (cleanedBin.attr) { + for (const labelKey in cleanedBin.attr) { + if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { + delete cleanedBin.attr[labelKey]; + } + } + if (Object.keys(cleanedBin.attr).length === 0) { + cleanedBin.attr = void 0; + } + } + compactedMap.set(damageValue, cleanedBin); + } + return new _PMF(compactedMap, eps, this.normalized, this.identifier); + } + // Note: The "support" of a PMF is the set of all non-zero probability outcomes. + // This returns all damage values with non-zero probability, sorted ascending. + support() { + if (this._support === void 0) { + this._support = [...this.map.keys()].sort((a, b) => a - b); + } + return this._support; + } + // Minimum possible damage value. + min() { + if (this._min === void 0) { + const support = this.support(); + this._min = support.length > 0 ? support[0] : 0; + } + return this._min; + } + // Maximum possible damage value. + max() { + if (this._max === void 0) { + const support = this.support(); + this._max = support.length > 0 ? support[support.length - 1] : 0; + } + return this._max; + } + /** + * Returns the expected (mean) damage value. + * Cached for performance since this requires iterating through all bins. + */ + mean() { + if (this._mean === void 0) { + let totalSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + totalSum += damageValue * probabilityBin.p; + } + this._mean = totalSum; + } + return this._mean; + } + /** + * Returns the variance of the damage distribution. + * Cached for performance since this requires mean calculation plus iteration. + */ + variance() { + if (this._variance === void 0) { + const meanValue = this.mean(); + let varianceSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + const deviationFromMean = damageValue - meanValue; + varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; + } + this._variance = varianceSum; + } + return this._variance; + } + /** + * Returns the standard deviation of the damage distribution. + */ + stdev() { + if (this._stdev === void 0) { + this._stdev = Math.sqrt(this.variance()); + } + return this._stdev; + } + /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ + static cloneBin(bin) { + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ + static scaleBin(bin, factor) { + const count = {}; + for (const k in bin.count) { + count[k] = bin.count[k] * factor; + } + let attr; + if (bin.attr) { + attr = {}; + for (const k in bin.attr) { + attr[k] = bin.attr[k] * factor; + } + } + return { p: bin.p * factor, count, attr }; + } + static mergeInto(destinationMap, damageValue, binToAdd) { + const existingBin = destinationMap.get(damageValue); + if (!existingBin) { + destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); + return; + } + existingBin.p += binToAdd.p; + for (const labelKey in binToAdd.count) { + existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; + } + if (binToAdd.attr) { + if (!existingBin.attr) { + existingBin.attr = {}; + } + for (const labelKey in binToAdd.attr) { + existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; + } + } + } + // Convenience method + add(other) { + return this.addScaled(other, 1); + } + /** + * Returns a new PMF with a scaled branch added to this one. + * The branch PMF is scaled by the given probability before merging + * This will be very useful for conditional effects and for being + * able to model "I can probably have this opportunity attack 40% of rounds" + * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes + */ + addScaled(branch, probability) { + if (probability === 0) return this; + const resultMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of this.map) { + resultMap.set(dmg, _PMF.cloneBin(bin)); + } + for (const [damageValue, probabilityBin] of branch.map) { + _PMF.mergeInto( + resultMap, + damageValue, + _PMF.scaleBin(probabilityBin, probability) + ); + } + return new _PMF( + resultMap, + this.epsilon, + false, + `${this.identifier}+scaled(${branch.identifier},${probability})` + ); + } + /** + * Redistributes probability mass to model an effect that only occurs with + * probability `frequency` — a conditional attack, an on-hit rider, or a + * sub-one AoE target fraction. + * + * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, + * per-label `count`, AND per-label `attr` — and the freed mass is moved into + * the miss bin at damage 0, tagged with the canonical `missNone` outcome. + * Total probability mass is preserved. + * + * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage + * attribution (`attr`) intact, so a frequency-scaled PMF still renders + * correctly in the damage-attribution charts. + * + * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` + * collapses all mass into the miss bin. The miss outcome is assumed to be + * encoded at damage value 0. + * + * @param frequency Probability in [0, 1] that the effect occurs. + */ + applyHitFrequency(frequency) { + if (!Number.isFinite(frequency) || frequency >= 1) return this; + const freq = Math.max(0, frequency); + const pMiss = this.pAt(0); + const pHit = 1 - pMiss; + const newMissMass = pMiss + (1 - freq) * pHit; + const newMap = /* @__PURE__ */ new Map(); + newMap.set(0, { + p: newMissMass, + count: { [MISS_NONE_OUTCOME]: newMissMass }, + attr: {} + }); + for (const [damage, bin] of this.map) { + if (damage <= 0) continue; + newMap.set(damage, _PMF.scaleBin(bin, freq)); + } + return new _PMF( + newMap, + this.epsilon, + false, + `freq(${this.identifier},${freq})` + ); + } + scaleMass(factor) { + if (factor === 1) return this; + const scaledMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); + } + return new _PMF( + scaledMap, + this.epsilon, + false, + `scale(${this.identifier},${factor})` + ); + } + mapDamage(damageTransformFunction) { + const transformedMap = /* @__PURE__ */ new Map(); + for (const [originalDamage, probabilityBin] of this.map) { + const transformedDamage = damageTransformFunction(originalDamage); + _PMF.mergeInto( + transformedMap, + transformedDamage, + _PMF.cloneBin(probabilityBin) + ); + } + return new _PMF( + transformedMap, + this.epsilon, + this.normalized, + `map(${this.identifier})` + ); + } + scaleDamage(factor, rounding = "floor") { + const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; + return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); + } + getPMFCombineCacheKey(p1, p2, eps, raw) { + const [id1, id2] = [p1.identifier, p2.identifier].sort(); + return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; + } + /** + * A small content fingerprint (mass + bin count + face sum) so convolution + * cache keys change if the underlying numbers do. Memoized because a PMF is + * immutable once constructed — this avoids re-summing every key on each + * convolve() call (including cache hits). + */ + fingerprint() { + if (this._fingerprint === void 0) { + let faceSum = 0; + for (const k of this.map.keys()) faceSum += k; + this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; + } + return this._fingerprint; + } + convolve(other, eps, raw = false) { + const epsilon = eps ?? this.epsilon; + const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); + const A0 = norm(this); + const B0 = norm(other); + const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; + const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); + const cached = pmfCache?.get(cacheKey); + if (cached) return cached; + const combinedMap = /* @__PURE__ */ new Map(); + for (const [aVal, aBin] of A.map) { + const ap = aBin.p; + const aCount = aBin.count; + const aAttr = aBin.attr; + for (const [bVal, bBin] of B.map) { + const bp = bBin.p; + const dmg = aVal + bVal; + let dest = combinedMap.get(dmg); + if (dest === void 0) { + dest = { p: 0, count: {} }; + combinedMap.set(dmg, dest); + } + dest.p += ap * bp; + const dc = dest.count; + for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; + for (const k in bBin.count) + dc[k] = (dc[k] || 0) + bBin.count[k] * ap; + if (aAttr || bBin.attr) { + let da = dest.attr; + if (da === void 0) { + da = {}; + dest.attr = da; + } + if (aAttr) + for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; + if (bBin.attr) + for (const k in bBin.attr) + da[k] = (da[k] || 0) + bBin.attr[k] * ap; + } + } + } + let result = new _PMF( + combinedMap, + epsilon, + !raw, + `${A.identifier}${raw ? "*" : "+"}${B.identifier}` + ); + const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); + const mGot = result.mass(); + if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { + result = result.scaleMass(mExp / mGot); + } + if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) + result = result.normalize(); + pmfCache?.set(cacheKey, result); + return result; + } + // 3) Nice wrapper so you can call pmf.combineRaw(other) + combineRaw(other, eps) { + return this.convolve(other, eps, true); + } + // Reduce a list of PMFs by left-folding convolve() with the given eps + static reduceConvolveLeft(pmfList, eps) { + let result = pmfList[0]; + for (let i = 1; i < pmfList.length; i++) { + result = result.convolve(pmfList[i], eps); + } + return result; + } + /** + * Convolves multiple PMFs using linear convolution with automatic caching. + * Uses a left-to-right accumulation approach for maximum cache reuse. + * Each convolve() call automatically uses the convolution cache for performance. + * + * This linear approach provides better cache hits than pairwise because: + * - Intermediate results are more predictable and stable + * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) + * - Order-independent cache keys work better with consistent build patterns + */ + static convolveMany(pmfList, eps = EPS) { + if (pmfList.length === 0) return _PMF.empty(eps); + if (pmfList.length === 1) return pmfList[0]; + return _PMF.reduceConvolveLeft(pmfList, eps); + } + /** + * Returns a plain, JSON-serializable representation of this PMF. + * + * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces + * the expected output (no double-encoding). Use {@link PMF.fromJSON} to + * reconstruct, or {@link PMF.toJSONString} if you need the string directly. + */ + toJSON() { + return { + bins: [...this.map.entries()], + normalized: this.normalized, + identifier: this.identifier + }; + } + /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ + toJSONString() { + return JSON.stringify(this); + } + static fromJSON(jsonData) { + return new _PMF( + new Map(jsonData.bins), + EPS, + !!jsonData.normalized, + jsonData.identifier || "fromJSON" + ); + } + /** + * Relative pruning with optional top-K floor. + * Keeps bins with p >= epsRel * peak, always keeps min and max damage, + * optionally guarantees at least `minBins` survivors by adding top-K. + * Returns a new, non-normalized PMF. + */ + prune(epsRel, minBins = 0) { + const size = this.map.size; + if (size === 0) return this; + let peak = 0; + let minDamage = Number.POSITIVE_INFINITY; + let maxDamage = Number.NEGATIVE_INFINITY; + for (const [dmg, bin] of this.map) { + if (bin.p > peak) peak = bin.p; + if (dmg < minDamage) minDamage = dmg; + if (dmg > maxDamage) maxDamage = dmg; + } + if (peak === 0) + return new _PMF(new Map(this.map), epsRel, false, this.identifier); + const thresh = epsRel * peak; + const entries = [...this.map.entries()]; + const survivorsByDmg = /* @__PURE__ */ new Map(); + const protect = (d) => { + const b = this.map.get(d); + if (b) survivorsByDmg.set(d, b); + }; + protect(minDamage); + if (maxDamage !== minDamage) protect(maxDamage); + for (const [dmg, bin] of entries) { + if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); + } + if (minBins > 0 && survivorsByDmg.size < minBins) { + entries.sort((a, b) => b[1].p - a[1].p); + for (const [dmg, bin] of entries) { + if (!survivorsByDmg.has(dmg)) { + survivorsByDmg.set(dmg, bin); + if (survivorsByDmg.size >= minBins) break; + } + } + } + const prunedMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of survivorsByDmg) { + const newCount = {}; + for (const k in bin.count) { + const v = bin.count[k]; + if (Math.abs(v) >= thresh) newCount[k] = v; + } + let newAttr; + if (bin.attr) { + for (const k in bin.attr) { + const v = bin.attr[k]; + if (Math.abs(v) >= thresh) { + if (!newAttr) newAttr = {}; + newAttr[k] = v; + } + } + } + prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); + } + return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); + } + /** Probability mass at exactly x. */ + pAt(x) { + return this.map.get(x)?.p ?? 0; + } + /** + * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. + * Assumes a miss is encoded as the damage-0 bin (the convention used across + * attack/save PMFs). The dual of {@link missProbability}. + */ + hitProbability() { + return 1 - this.pAt(0); + } + /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ + missProbability() { + return this.pAt(0); + } + /** + * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width + * damage buckets, aggregating probability mass (and `count`/`attr` + * provenance) into each bucket's start value. Returns this PMF unchanged when + * its integer support already fits within `maxBuckets`. + * + * This is a lossy display/downsampling transform (bucket start replaces the + * exact damage value) — use it for charting wide distributions, not for DPR + * math. + */ + rebin(maxBuckets) { + if (!(maxBuckets > 0)) return this; + const support = this.support(); + if (support.length === 0) return this; + const min = support[0]; + const max = support[support.length - 1]; + const range = max - min; + if (range + 1 <= maxBuckets) return this; + const binSize = Math.ceil((range + 1) / maxBuckets); + return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize); + } + /** Dense integer support from min..max (inclusive). + * Useful for showing empty bars in charts. + */ + denseSupport() { + const s = this.support(); + if (s.length === 0) return []; + const lo = Math.min(...s), hi = Math.max(...s); + return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( + (a, b) => a - b + ); + } + /** CDF at x: P(X ≤ x). */ + cdfAt(x) { + let acc = 0; + for (const [val, bin] of this.map) if (val <= x) acc += bin.p; + return acc; + } + /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ + quantile(p) { + if (this.map.size === 0) return 0; + const s = this.support().sort((a, b) => a - b); + let acc = 0; + for (const x of s) { + acc += this.pAt(x); + if (acc >= p) return x; + } + return s[s.length - 1]; + } + /** Get outcome probability at specific damage value. */ + outcomeAt(damage, outcome) { + return this.map.get(damage)?.count[outcome] ?? 0; + } + /** Get all outcome types present in this PMF. */ + outcomes() { + const outcomeSet = /* @__PURE__ */ new Set(); + for (const [, bin] of this.map) { + for (const outcome in bin.count) { + if (bin.count[outcome] > 0) { + outcomeSet.add(outcome); + } + } + } + return Array.from(outcomeSet).sort(); + } + /** Get total probability of an outcome across all damage values. */ + outcomeProbability(outcome) { + let total = 0; + for (const [, bin] of this.map) { + total += bin.count[outcome] ?? 0; + } + return total; + } + /** Get damage attribution for an outcome at specific damage value. */ + outcomeAttributionAt(damage, outcome) { + return this.map.get(damage)?.attr?.[outcome] ?? 0; + } + /** Get all outcome data at specific damage value. */ + binAt(damage) { + const bin = this.map.get(damage); + if (!bin) return null; + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Check if outcome exists in this PMF. */ + hasOutcome(outcome) { + for (const [, bin] of this.map) { + if ((bin.count[outcome] ?? 0) > 0) { + return true; + } + } + return false; + } + /** + * Split each damage value's probability mass across outcome labels, returning + * per-label maps of `damage value → probability mass attributable to that + * label`. Summing over labels at a given value recovers that value's `p`. + * + * Damage-bearing bins are split by `attr` weight (the share of damage each + * outcome contributed); the clean-miss bin at 0 is split by `count` weight + * (there is no damage to attribute). Attribution is computed on demand via + * {@link withAttribution} when absent, so builder-generated PMFs work too. + * + * This is the provenance core of the stacked damage-attribution chart — the + * caller only maps these series into its rendering format (colors, binning, + * axis labels). + */ + attributionByValue() { + const src = this.hasAttribution() ? this : this.withAttribution(); + const result = /* @__PURE__ */ new Map(); + const add = (label, damage, mass) => { + if (!(mass > 0)) return; + let series = result.get(label); + if (!series) { + series = /* @__PURE__ */ new Map(); + result.set(label, series); + } + series.set(damage, (series.get(damage) ?? 0) + mass); + }; + for (const [damage, bin] of src.map) { + const p = bin.p || 0; + if (p <= 0) continue; + const isMissBin = damage === 0; + if (isMissBin) { + let totalCount = 0; + for (const k in bin.count) totalCount += bin.count[k] || 0; + if (totalCount > 0) { + const c = bin.count[MISS_NONE_OUTCOME] || 0; + add(MISS_NONE_OUTCOME, damage, c / totalCount * p); + } + continue; + } + let totalAttr = 0; + if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; + if (bin.attr && totalAttr > 0) { + for (const k in bin.attr) { + if (k === MISS_NONE_OUTCOME) continue; + add(k, damage, (bin.attr[k] || 0) / totalAttr * p); + } + } + } + return result; + } + tailProbGE(t) { + let s = 0; + for (const [x, bin] of this) { + if (bin.p > 0 && x >= t) s += bin.p; + } + return s; + } + tailProbGT(t) { + let s = 0; + for (const [x, rec] of this) { + if (x > t) s += rec.p; + } + return s; + } + /** + * Returns a new PMF containing only bins where the specified outcome has non-zero probability. + * This creates a marginal distribution for the given outcome type, with probabilities + * scaled to represent the unconditional mass attributable to that outcome. + */ + filterOutcome(outcome) { + const filteredMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of this.map) { + const outcomeCount = bin.count[outcome] ?? 0; + const totalCount = Object.values(bin.count ?? {}).reduce( + (a, b) => (a ?? 0) + (b ?? 0), + 0 + ); + if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { + const proportion = outcomeCount / totalCount; + const newP = bin.p * proportion; + const newCount = { [outcome]: outcomeCount }; + let newAttr; + if (bin.attr && bin.attr[outcome] !== void 0) { + newAttr = { [outcome]: bin.attr[outcome] * proportion }; + } + filteredMap.set(damageValue, { + p: newP, + count: newCount, + attr: newAttr + }); + } + } + return new _PMF( + filteredMap, + this.epsilon, + false, + // don't normalize by default + `filter(${this.identifier},${outcome})` + ); + } + /** + * Calculates probabilities for first-success outcomes across n independent attempts. + * + * @param pSuccess - Total probability of any success on a single attempt. + * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). + * @param n - Number of independent attempts. + * + * Returns: + * - pSpecificSuccess: Probability that the first success was of the "special" type + * - pGeneralSuccess: Probability that the first success was of the non-special type + * - pNone: Probability that no successes occurred + * - pAny: Probability that at least one success occurred + */ + static firstSuccessWeights(pSuccess, pSpecial, n) { + if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { + throw new Error( + `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` + ); + } + const pFail = 1 - pSuccess; + const pFailAll = Math.pow(pFail, n); + const pAny = 1 - pFailAll; + const denom = pSuccess === 0 ? 1 : pSuccess; + const pSpecificSuccess = pSpecial * pAny / denom; + const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; + const pNone = 1 - pSpecificSuccess - pGeneralSuccess; + return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; + } + mapValues(f, eps = EPS, opts) { + const rounding = opts?.rounding ?? "none"; + const preserveCounts = opts?.preserveCounts ?? true; + const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; + const probs = /* @__PURE__ */ new Map(); + const counts = /* @__PURE__ */ new Map(); + for (const [v, bin] of this) { + if (Math.abs(bin.p) < eps) continue; + const u = round(f(v)); + probs.set(u, (probs.get(u) ?? 0) + bin.p); + if (preserveCounts) { + const src = bin.count; + if (src) { + const dest = counts.get(u) ?? {}; + for (const k in src) { + dest[k] = (dest[k] ?? 0) + src[k]; + } + counts.set(u, dest); + } + } + } + const internal = /* @__PURE__ */ new Map(); + for (const [u, p] of probs) { + internal.set(u, { p, count: counts.get(u) ?? {} }); + } + return _PMF.fromMap( + new Map(Array.from(internal, ([u, b]) => [u, b.p])), + eps + ); + } + static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { + const filtered = []; + for (const [v, p] of m) { + if (!Number.isFinite(v) || !Number.isFinite(p)) continue; + if (p <= 0 || Math.abs(p) < eps) continue; + if (requireIntegerValues && !Number.isInteger(v)) { + throw new Error(`fromMap: non-integer outcome ${v}`); + } + filtered.push([v, p]); + } + if (filtered.length === 0) { + throw new Error("fromMap: empty or invalid input map"); + } + let sum = 0; + let c = 0; + for (const [, p] of filtered) { + const y = p - c; + const t = sum + y; + c = t - sum - y; + sum = t; + } + if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); + filtered.sort((a, b) => a[0] - b[0]); + const internal = /* @__PURE__ */ new Map(); + for (const [v, p] of filtered) { + internal.set(v, { p: p / sum, count: {} }); + } + return new _PMF(internal, eps); + } + query() { + return new DiceQuery(this); + } +}; +// Unique ID generator for anonymous PMFs to avoid cache key collisions +_PMF.__anonIdCounter = 1; +var PMF = _PMF; + +// src/parser/dice.ts +var MAX_BINARY_OUTCOMES = 1e8; +var Dice = class _Dice { + constructor(x = 0) { + this.faces = {}; + this.privateData = {}; + // Partial: the object starts empty and gains keys as outcomes are recorded, + // so the type must not claim every OutcomeType is present. (Previously typed + // as a full Record via an `as` cast, which lied about missing keys.) + this.outcomeData = {}; + this.hasHitDistributionCalculated = false; + if (x <= 0) return; + for (let i = 1; i <= x; i++) { + this.faces[i] = 1; + } + } + getOutcomeDistribution(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const distribution = this.outcomeData[key]; + if (distribution === void 0) return void 0; + return { ...distribution }; + } + getFullOutcomeDistribution() { + return { ...this.outcomeData }; + } + setOutcomeDistribution(key, data) { + if (data) { + this.outcomeData[key] = data; + } else { + delete this.outcomeData[key]; + } + } + hasOutcomeData(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const data = this.outcomeData[key]; + return data !== void 0 && Object.keys(data).length > 0; + } + getOutcomeCount(key, face) { + return this.outcomeData[key]?.[face] ?? 0; + } + getAverage(key) { + const distribution = this.getOutcomeDistribution(key); + if (!distribution) return 0; + const totalCount = Object.values(distribution).reduce( + (sum, count) => sum + count, + 0 + ); + const expectedDamage = Object.entries(distribution).reduce( + (sum, [damage, count]) => sum + Number(damage) * count, + 0 + ); + if (totalCount === 0) return 0; + return expectedDamage / totalCount; + } + // TODO this can be private later if we change how testing works + calculateHitDistribution() { + const hitValues = {}; + const subtractedOutcomes = [ + this.outcomeData.crit, + this.outcomeData.missNone, + this.outcomeData.missDamage, + this.outcomeData.saveHalf, + this.outcomeData.saveFail, + this.outcomeData.pc + ]; + for (const [face, totalCount] of Object.entries(this.faces)) { + const numFace = Number(face); + let hitCount = totalCount; + for (const distribution of subtractedOutcomes) { + const outcomeCount = distribution?.[numFace]; + if (outcomeCount) { + hitCount -= outcomeCount; + } + } + if (numFace === 0) { + hitCount = 0; + } + if (hitCount < 0) { + hitCount = 0; + } + hitValues[numFace] = hitCount; + } + return hitValues; + } + ensureHitDistribution() { + if (!this.hasHitDistributionCalculated) { + const hitValues = this.calculateHitDistribution(); + this.setOutcomeDistribution("hit", hitValues); + this.hasHitDistributionCalculated = true; + } + } + // PRIVATE FUNCTIONS + binaryOp(other, op, diceConstructor) { + const result = diceConstructor ? diceConstructor() : new _Dice(); + const isScalar = typeof other === "number"; + const keys1 = this.keys(); + const keys2 = isScalar ? [] : other.keys(); + if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { + throw new DiceParseError( + `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` + ); + } + for (const key1 of keys1) { + const value1 = this.faces[key1]; + if (isScalar) { + const resultKey = op(key1, other); + result.increment(resultKey, value1); + } else { + for (const key2 of keys2) { + const value2 = other.faces[key2]; + const resultKey = op(key1, key2); + result.increment(resultKey, value1 * value2); + } + } + } + return result; + } + removeFaces(facesToRemove) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (!facesToRemove.includes(numKey)) { + result.faces[numKey] = value; + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // PUBLIC FUNCTIONS + getFaceEntries() { + return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); + } + getFaceMap() { + return { ...this.faces }; + } + get(face) { + return this.faces[face] ?? 0; + } + keys() { + return Object.keys(this.faces).map(Number); + } + values() { + return Object.values(this.faces); + } + total() { + return Object.values(this.faces).reduce((sum, value) => sum + value, 0); + } + setFace(key, value) { + this.faces[key] = value; + } + static scalar(value) { + const result = new _Dice(); + result.increment(value, 1); + return result; + } + maxFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.max(...numericKeys); + } + minFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.min(...numericKeys); + } + increment(face, count) { + const current = this.faces[face] || 0; + this.faces[face] = current + count; + } + normalize(scalar) { + const result = new _Dice(); + for (const [face, count] of Object.entries(this.faces)) { + result.faces[Number(face)] = count * scalar; + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // OPERATIONS + add(other) { + return this.binaryOp(other, (a, b) => a + b); + } + subtract(other) { + return this.binaryOp(other, (a, b) => a - b); + } + conditionalApply(other) { + return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); + } + multiply(other) { + return this.binaryOp(other, (a, b) => a * b); + } + addNonZero(other) { + return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); + } + eq(other) { + return this.binaryOp(other, (a, b) => a === b ? 1 : 0); + } + max(other) { + return this.binaryOp(other, (a, b) => Math.max(a, b)); + } + min(other) { + return this.binaryOp(other, (a, b) => Math.min(a, b)); + } + advantage() { + return this.max(this); + } + ge(other) { + return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); + } + divide(other) { + return this.binaryOp(other, (a, b) => a / b); + } + divideRoundUp(other) { + return this.binaryOp(other, (a, b) => Math.ceil(a / b)); + } + divideRoundDown(other) { + return this.binaryOp(other, (a, b) => Math.floor(a / b)); + } + and(other) { + return this.binaryOp(other, (a, b) => a && b ? 1 : 0); + } + checkTarget(other, comparisonLogic) { + const createResult = () => { + const result = new _Dice(); + result.increment(0, 0); + result.increment(1, 0); + return result; + }; + return this.binaryOp(other, comparisonLogic, createResult); + } + dc(other) { + const dcCheck = (a, b) => a >= b ? 0 : 1; + const result = this.checkTarget(other, dcCheck); + result.privateData.isDCCheck = true; + return result; + } + ac(other) { + const acCheck = (a, b) => a >= b ? a : 0; + return this.checkTarget(other, acCheck); + } + deleteFace(face) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (numKey !== face) { + result.increment(numKey, value); + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + reroll(toReroll) { + const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; + const rerollKeys = rerollDice.keys(); + const rerollSet = new Set(rerollKeys); + const removed = this.removeFaces(rerollKeys); + let result = new _Dice(); + for (const face of this.keys()) { + const wasRerolled = rerollSet.has(face); + result = result.combine(removed); + if (wasRerolled) { + result = result.combine(this); + } + } + return result; + } + // This is not addition and not rolling two dice at once. + // Instead, it’s mixing two distributions into a single weighted die. + combine(other) { + if (typeof other === "number") { + other = _Dice.scalar(other); + } + const result = new _Dice(); + for (const [key, value] of Object.entries(other.faces)) { + result.faces[Number(key)] = value; + } + const except = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + result.increment(numKey, value); + if (!(numKey in other.faces)) { + except.increment(numKey, value); + } + } + result.privateData = { ...this.privateData, except: other }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + combineInPlace(other) { + for (const [key, value] of Object.entries(other.faces)) { + const numKey = Number(key); + const current = this.faces[numKey] || 0; + this.faces[numKey] = current + value; + } + } + percent() { + const total = this.total(); + const result = {}; + for (const [face, count] of Object.entries(this.faces)) { + result[Number(face)] = count / total; + } + return result; + } + average() { + const total = this.total(); + if (total === 0) return 0; + let sum = 0; + for (const [key, value] of Object.entries(this.faces)) { + sum += Number(key) * value; + } + return sum / total; + } + /* + * Convert dice to PMF using OutcomeType labels directly from damage distribution. + * This is much cleaner than the original complex distribution conversion. + */ + toPMF(numEpsilon = EPS) { + const total = this.total(); + if (total === 0) return PMF.empty(numEpsilon); + this.ensureHitDistribution(); + const map = /* @__PURE__ */ new Map(); + const hitDistro = this.getOutcomeDistribution("hit") || {}; + const critDistro = this.getOutcomeDistribution("crit") || {}; + const missDistro = this.getOutcomeDistribution("missDamage") || {}; + const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; + const pcDistro = this.getOutcomeDistribution("pc") || {}; + const isSaveHalf = Object.keys(saveDistro).length > 0; + const isDCCheck = this.privateData.isDCCheck === true; + const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; + for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { + const face = Number(faceStr); + const faceCount = Number(faceCountRaw); + if (faceCount <= 0) continue; + let p = faceCount / total; + p = clampNonNeg(p); + if (!(p > 0)) continue; + if (numEpsilon >= 0 && p < numEpsilon) continue; + const count = {}; + const attr = {}; + if (hitDistro[face]) { + const c = clampNonNeg(hitDistro[face] / total); + if (c > 0) { + if (isSaveHalf || isDCCheck) { + count.saveFail = c; + attr.saveFail = clampNonNeg(face * hitDistro[face] / total); + } else { + count.hit = c; + attr.hit = clampNonNeg(face * hitDistro[face] / total); + } + } + } + if (critDistro[face]) { + const c = clampNonNeg(critDistro[face] / total); + if (c > 0) { + count.crit = c; + attr.crit = clampNonNeg(face * critDistro[face] / total); + } + } + if (missDistro[face]) { + const c = clampNonNeg(missDistro[face] / total); + if (c > 0) { + count.missDamage = c; + attr.missDamage = clampNonNeg(face * missDistro[face] / total); + } + } + if (saveDistro[face]) { + const c = clampNonNeg(saveDistro[face] / total); + if (c > 0) { + if (isSaveHalf) { + count.saveHalf = c; + attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); + } else { + count.saveFail = (count.saveFail ?? 0) + c; + attr.saveFail = clampNonNeg( + (attr.saveFail ?? 0) + face * saveDistro[face] / total + ); + } + } + } + if (pcDistro[face]) { + const c = clampNonNeg(pcDistro[face] / total); + if (c > 0) { + count.pc = c; + attr.pc = clampNonNeg(face * pcDistro[face] / total); + } + } + if (!isSaveHalf && !isDCCheck) { + const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); + const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); + if (unaccountedCount > 0) { + const frac = clampNonNeg(unaccountedCount / total); + if (frac > 0) { + count.missNone = (count.missNone ?? 0) + frac; + } + } + } + const bin = { p, count }; + if (Object.keys(attr).length > 0) { + bin.attr = attr; + } + map.set(face, bin); + } + const identifier = this.identifier || "ERROR"; + return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); + } +}; + +// src/parser/parser.ts +var MAX_DIE_SIDES = 1e6; +var MAX_DICE_COUNT = 1e4; +var MAX_KEEP_OUTCOMES = 1e6; +var parseCache = new LRUCache(1e3); +var cachingEnabled = true; +function setCachingEnabled(enabled) { + cachingEnabled = enabled; + if (!enabled) clearParserCache(); +} +function getCachingEnabled() { + return cachingEnabled; +} +function clearParserCache() { + parseCache.clear(); +} +function parse(expression, n = 0) { + const cleaned = expression.replace(/ /g, "").toLowerCase(); + if (cachingEnabled) { + const cacheKey = `${cleaned}:${n}`; + const cached = parseCache.get(cacheKey); + if (cached) return cached; + } + const chars = [...cleaned]; + let result; + try { + result = parseExpression(chars, n); + } catch (error) { + throw new DiceParseError( + `Cannot parse dice expression [${expression}]: ${error}`, + { expression, cause: error } + ); + } + result.privateData = result.privateData || {}; + result.identifier = cleaned; + if (chars.length > 0) { + throw new DiceParseError( + `Unexpected token: '${chars[0]}' from expression: '${expression}'`, + { expression } + ); + } + const resultPMF = result.toPMF(-1); + if (cachingEnabled) { + const cacheKey = `${cleaned}:${n}`; + parseCache.set(cacheKey, resultPMF); + } + return resultPMF; +} +function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { + dice = dice.normalize(currentNorm); + finalResult = finalResult.normalize(normValue); + finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); + finalResult = finalResult.combine(dice); + return { newNorm: currentNorm * normValue, updatedResult: finalResult }; +} +function parseExpression(arr, n) { + const result = (() => { + const res = parseArgument(arr, n); + return typeof res === "number" ? Dice.scalar(res) : res; + })(); + let op = parseOperation(arr); + let finalResult = result; + while (op != null) { + const arg = !op.unary ? parseArgument(arr, n) : finalResult; + let crit; + let critNorm = 1; + if (arr[0] === "x" || arr[0] === "c") { + const isXcrit = arr[0] === "x"; + if (isXcrit) assertToken(arr, "x"); + assertToken(arr, "c"); + assertToken(arr, "r"); + assertToken(arr, "i"); + assertToken(arr, "t"); + const count = isXcrit ? parseNumber(arr, n) : 1; + crit = new Dice(); + for (let i = 0; i < count; i++) { + const max = finalResult.maxFace(); + crit.setFace(max, finalResult.get(max)); + finalResult = finalResult.deleteFace(max); + } + critNorm = crit.total(); + crit = op.call(crit, parseBinaryArgument(arg, arr, n)); + critNorm = crit && critNorm ? crit.total() / critNorm : 1; + } + let save; + let saveNorm = 1; + if (arr[0] === "s") { + assertToken(arr, "s"); + assertToken(arr, "a"); + assertToken(arr, "v"); + assertToken(arr, "e"); + save = new Dice(); + const min = finalResult.minFace(); + save.increment(min > 0 ? min : 1, finalResult.get(min)); + saveNorm = save.total(); + finalResult = finalResult.deleteFace(min); + save = op.call(save, parseBinaryArgument(arg, arr, n)); + saveNorm = save && saveNorm ? save.total() / saveNorm : 1; + } + let pc; + let pcNorm = 1; + if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { + assertToken(arr, "p"); + assertToken(arr, "c"); + pc = new Dice(); + const min = finalResult.minFace(); + pc.increment(min > 0 ? min : 1, finalResult.get(min)); + const missBefore = pc.total(); + finalResult = finalResult.deleteFace(min); + pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); + const missAfter = pc ? pc.total() : 0; + pcNorm = missBefore ? missAfter / missBefore : 1; + } + let miss; + let missNorm = 1; + if (arr[0] === "m") { + assertToken(arr, "m"); + assertToken(arr, "i"); + assertToken(arr, "s"); + assertToken(arr, "s"); + miss = new Dice(); + const min = finalResult.minFace(); + miss.increment(min > 0 ? min : 1, finalResult.get(min)); + missNorm = miss.total(); + finalResult = finalResult.deleteFace(min); + miss = op.call(miss, parseBinaryArgument(arg, arr, n)); + missNorm = miss && missNorm ? miss.total() / missNorm : 1; + } + let norm = finalResult.total(); + finalResult = op.call(finalResult, arg); + norm = norm ? finalResult.total() / norm : 1; + if (crit) { + const result2 = combineDiceWithNormalization( + crit, + critNorm, + "crit", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (save) { + const result2 = combineDiceWithNormalization( + save, + saveNorm, + "saveHalf", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (miss) { + const result2 = combineDiceWithNormalization( + miss, + missNorm, + "missDamage", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (pc) { + const result2 = combineDiceWithNormalization( + pc, + pcNorm, + "pc", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + op = parseOperation(arr); + } + return finalResult; +} +function parseArgument(s, n) { + let result = parseArgumentInternal(s, n); + while (true) { + const next = parseArgumentInternal(s, n); + if (next === void 0) break; + result = multiplyDiceByDice(result, next); + } + return result; +} +function multiplyDiceByDice(d1, d2) { + if (typeof d1 === "number") d1 = Dice.scalar(d1); + if (typeof d2 === "number") d2 = Dice.scalar(d2); + const result = new Dice(); + const faces = /* @__PURE__ */ new Map(); + let normalizationFactor = 1; + for (const key of d1.keys()) { + let face; + if (typeof key !== "number") { + continue; + } + if (d2.privateData.keep) { + const faceCount = d2.keys().length; + if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { + throw new DiceParseError( + `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` + ); + } + const repeat = Array(key).fill(d2); + face = opDice(repeat, d2.privateData.keep); + } else { + face = multiplyDice(key, d2); + } + normalizationFactor *= face.total(); + faces.set(key, face); + } + for (const [k, face] of faces) { + const count = d1.get(k); + result.combineInPlace( + face.normalize(count * normalizationFactor / face.total()) + ); + } + result.privateData.except = {}; + return result; +} +function multiplyDice(n, d) { + if (n > MAX_DICE_COUNT) { + throw new DiceParseError( + `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` + ); + } + if (n === 0) return new Dice(0); + if (n === 1) return d; + const half = Math.floor(n / 2); + let result = multiplyDice(half, d); + result = result.add(result); + if (n % 2 === 1) { + result = result.add(d); + } + return result; +} +function opDice(diceList, keepFn) { + return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); +} +function opDiceInternal(diceList, result, index, values, weight, combineFn) { + if (index === diceList.length) { + return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); + } + const currentDice = diceList[index]; + for (const face of currentDice.keys()) { + values.push(face); + result = opDiceInternal( + diceList, + result, + index + 1, + values, + weight * currentDice.get(face), + combineFn + ); + values.pop(); + } + return result; +} +function parseArgumentInternal(s, n) { + if (s.length === 0) return; + const c = s[0]; + switch (c) { + case "(": + s.shift(); + return assertToken(s, ")", parseExpression(s, n)); + case "h": + case "d": + return parseDice(s, n); + case "k": + assertToken(s, "k"); + return parseKeep(s, n); + case "n": + return parseNumber(s, n); + default: + if (isDigit(c)) return parseNumber(s, n); + return; + } +} +function parseBinaryArgument(arg, arr, n) { + if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { + assertToken(arr, "half"); + const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; + return diceArg.divideRoundDown(2); + } + const parsed = parseArgument(arr, n); + return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; +} +function assertToken(s, expected, ret) { + for (const ch of expected) { + const found = s.shift(); + if (found !== ch) { + throw new Error(`Expected character '${ch}', found '${found}'`); + } + } + return ret; +} +function parseDice(s, n) { + let rerollOne = false; + if (peek(s, "hd") && peekIsNumber(s, 2)) { + assertToken(s, "h"); + assertToken(s, "d"); + rerollOne = true; + } else if (peek(s, "d") && peekIsNumber(s, 1)) { + assertToken(s, "d"); + } else { + return; + } + const sides = parseNumber(s, n); + if (sides > MAX_DIE_SIDES) { + throw new DiceParseError( + `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` + ); + } + let result = new Dice(sides); + if (rerollOne) { + result = result.reroll(1); + } + return result; +} +function peek(arr, expected) { + if (expected.length > arr.length) return false; + for (let i = 0; i < expected.length; i++) { + if (arr[i] !== expected.charAt(i)) return false; + } + return true; +} +function peekIsNumber(arr, index) { + if (index >= arr.length) return false; + return isDigit(arr[index]) || arr[index] === "n"; +} +function parseNumber(s, n) { + let ret = ""; + while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { + const ch = s.shift(); + ret += ch === "n" ? n.toString() : ch; + } + if (ret.length === 0) { + throw new Error(`Expected number, found: '${s[0]}'`); + } + return parseInt(ret, 10); +} +function isDigit(c) { + return c >= "0" && c <= "9"; +} +function parseKeep(s, n) { + let keepLowest = false; + if (peek(s, "l")) { + assertToken(s, "l"); + keepLowest = true; + } else if (peek(s, "h")) { + assertToken(s, "h"); + keepLowest = false; + } else { + return; + } + const keepCount = parseNumber(s, n); + const result = parseArgumentInternal(s, n); + if (result instanceof Dice) { + result.privateData.keep = keepN(keepCount, keepLowest); + return result; + } + throw new Error("Expected Dice after keep modifier"); +} +function keepN(n, low) { + return (values) => { + const sorted = [...values].sort((a, b) => low ? a - b : b - a); + return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); + }; +} +function parseOperation(s) { + switch (s[0]) { + case ")": + return; + case "a": + assertToken(s, "ac"); + return Dice.prototype.ac; + case "d": + assertToken(s, "dc"); + return Dice.prototype.dc; + case "!": + assertToken(s, "!"); + const adv = Dice.prototype.advantage; + adv.unary = true; + return adv; + case ">": + assertToken(s, ">"); + return Dice.prototype.max; + case "<": + assertToken(s, "<"); + return Dice.prototype.min; + case "+": + assertToken(s, "+"); + return Dice.prototype.addNonZero; + case "~": + assertToken(s, "~"); + assertToken(s, "+"); + return Dice.prototype.add; + case "-": + assertToken(s, "-"); + return Dice.prototype.subtract; + case "&": + assertToken(s, "&"); + return Dice.prototype.combine; + case "r": + assertToken(s, "reroll"); + return Dice.prototype.reroll; + case "*": + assertToken(s, "*"); + if (peek(s, "*")) { + assertToken(s, "*"); + return Dice.prototype.multiply; + } + return Dice.prototype.conditionalApply; + case "/": + assertToken(s, "/"); + if (s[0] === "/") { + assertToken(s, "/"); + return Dice.prototype.divideRoundDown; + } + return Dice.prototype.divideRoundUp; + case "=": + assertToken(s, "="); + return Dice.prototype.eq; + } + return; +} + +// src/pmf/mixture.ts +var Mixture = class _Mixture { + constructor(eps = EPS) { + this.totals = /* @__PURE__ */ new Map(); + // raw mass per outcome (pre-normalization) + this.labelMass = /* @__PURE__ */ new Map(); + this.eps = Number.isFinite(eps) ? eps : EPS; + } + /** Remove all accumulated state. */ + clear() { + this.totals.clear(); + this.labelMass.clear(); + return this; + } + /** Number of distinct outcome values currently accumulated. */ + size() { + return this.totals.size; + } + /** Whether a label was ever added. */ + hasLabel(label) { + for (const bag of this.labelMass.values()) if (bag[label]) return true; + return false; + } + /** + * Add a labeled component with a mixture weight. + * Weight can be any positive finite number. Very small contributions are pruned by eps. + */ + add(label, pmf, weight = 1) { + if (!Number.isFinite(weight) || weight <= 0) return this; + for (const [v, bin] of pmf) { + const p = bin.p; + if (p <= 0) continue; + const add = weight * p; + if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; + this.totals.set(v, (this.totals.get(v) ?? 0) + add); + const bag = this.labelMass.get(v) ?? {}; + bag[label] = (bag[label] ?? 0) + add; + this.labelMass.set(v, bag); + } + return this; + } + buildPMF(eps = EPS) { + let grand = 0; + let c = 0; + for (const m of this.totals.values()) { + const y = m - c; + const t = grand + y; + c = t - grand - y; + grand = t; + } + if (!(grand > 0)) throw new Error("Mixture: zero total mass"); + const internal = /* @__PURE__ */ new Map(); + for (const [v, m] of this.totals) { + if (m <= 0 || Math.abs(m) < this.eps) continue; + const count = this.labelMass.get(v) ?? {}; + internal.set(v, { p: m / grand, count }); + } + return new PMF(internal, eps); + } + /** + * Produce normalized *per-label* PMFs (labels independent). + * These are unlabeled PMFs built from the raw mass of that label alone. + */ + byOutcome() { + const labels = /* @__PURE__ */ new Set(); + for (const bag of this.labelMass.values()) { + for (const k of Object.keys(bag)) labels.add(k); + } + const out = {}; + for (const label of labels) { + const m = /* @__PURE__ */ new Map(); + for (const [v, bag] of this.labelMass) { + const w = bag[label]; + if (w && Math.abs(w) >= this.eps) m.set(v, w); + } + if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); + } + return out; + } + /** + * Mixture weights per label, normalized to sum to 1 over labels that appeared. + * Uses raw mass before per-outcome normalization. + */ + weights() { + const res = {}; + for (const [, bag] of this.labelMass) { + for (const [lab, w] of Object.entries(bag)) { + if (!Number.isFinite(w) || w <= 0) continue; + res[lab] = (res[lab] ?? 0) + w; + } + } + let total = 0; + let c = 0; + for (const v of Object.values(res)) { + const y = v - c; + const t = total + y; + c = t - total - y; + total = t; + } + if (total > 0) { + for (const k in res) res[k] = res[k] / total; + } + return res; + } + toJSON() { + return { + totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), + labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), + eps: this.eps + }; + } + static mix(items, eps = EPS) { + const mix = new _Mixture(eps); + for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); + return mix.buildPMF(); + } +}; + +exports.ALL_OUTCOME_TYPES = ALL_OUTCOME_TYPES; +exports.DiceParseError = DiceParseError; +exports.DiceQuery = DiceQuery; +exports.EPS = EPS; +exports.LRUCache = LRUCache; +exports.MISS_NONE_OUTCOME = MISS_NONE_OUTCOME; +exports.Mixture = Mixture; +exports.OUTCOME_DISPLAY_ORDER = OUTCOME_DISPLAY_ORDER; +exports.PMF = PMF; +exports.calculateBounceOdds = calculateBounceOdds; +exports.clearParserCache = clearParserCache; +exports.critProbability = critProbability; +exports.getCachingEnabled = getCachingEnabled; +exports.onAnyHit = onAnyHit; +exports.onCritOnly = onCritOnly; +exports.onHitOnly = onHitOnly; +exports.onMissDamageOnly = onMissDamageOnly; +exports.onMissOnly = onMissOnly; +exports.onPotentCantripOnly = onPotentCantripOnly; +exports.onSaveFailOnly = onSaveFailOnly; +exports.onSaveHalfOnly = onSaveHalfOnly; +exports.parse = parse; +exports.pmfCache = pmfCache; +exports.setCachingEnabled = setCachingEnabled; +exports.sortOutcomes = sortOutcomes; +//# sourceMappingURL=index.cjs.map +//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/dist/index.cjs.map b/dist/index.cjs.map new file mode 100644 index 0000000..5c54f4d --- /dev/null +++ b/dist/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/common/bounce.ts","../src/common/errors.ts","../src/common/lru-cache.ts","../src/common/types.ts","../src/pmf/query.ts","../src/pmf/pmf.ts","../src/parser/dice.ts","../src/parser/parser.ts","../src/pmf/mixture.ts"],"names":["cumulativeSum","result"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Bounce odds — the \"birthday problem\" for bouncing damage dice (e.g. Chromatic\n * Orb): the probability that at least two of K dice with S faces show the same\n * value, which is what lets the spell jump to another target.\n *\n * Accounts for two modifiers:\n * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are\n * bumped up to it, collapsing the low faces onto a single heavier value.\n * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be\n * rerolled once, giving a second chance at a match.\n *\n * The base and Elemental-Adept cases are computed exactly (see\n * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered\n * on the exact base match probability.\n */\n\n/** Options that modify bounce odds via metamagic / feats. */\nexport interface BounceOddsOptions {\n /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */\n minimumDieRoll?: number;\n /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */\n rerollDamageDice?: number;\n}\n\n/** Binomial coefficient C(n, k), 0 for out-of-range k. */\nfunction binom(n: number, k: number): number {\n if (k < 0 || k > n) return 0;\n let result = 1;\n for (let i = 0; i < k; i++) result = (result * (n - i)) / (i + 1);\n return result;\n}\n\n/**\n * Exact P(all K dice show distinct values) for a die with `uniformCount`\n * ordinary faces (each probability `1/faces`) plus one optional heavy face whose\n * probability is `heavyWeight` (used for the Elemental-Adept collapse; pass 0\n * for a plain die). Uses the elementary symmetric polynomial e_K over the face\n * probabilities: P(all distinct) = K! · e_K.\n */\nfunction pAllDistinct(\n dice: number,\n faces: number,\n uniformCount: number,\n heavyWeight: number\n): number {\n const light = 1 / faces;\n // e_K = (choose K distinct light faces) + (heavy face + K-1 light faces).\n const eK =\n binom(uniformCount, dice) * Math.pow(light, dice) +\n heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1);\n let kFactorial = 1;\n for (let i = 2; i <= dice; i++) kFactorial *= i;\n return kFactorial * eK;\n}\n\n/** Exact P(at least one duplicate) among `dice` dice, honoring Elemental Adept. */\nfunction pMatch(dice: number, faces: number, minimumDieRoll: number): number {\n if (dice <= 1) return 0;\n if (dice > faces) return 1;\n\n if (minimumDieRoll >= 2) {\n // Rolls 1..minimumDieRoll collapse onto the value `minimumDieRoll`, giving it\n // weight minimumDieRoll/faces; the faces above it stay uniform at 1/faces.\n const uniformCount = faces - minimumDieRoll; // values minimumDieRoll+1 .. faces\n const effectiveValues = uniformCount + 1; // + the collapsed value\n if (dice > effectiveValues) return 1;\n const heavyWeight = minimumDieRoll / faces;\n const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight);\n return Math.min(1, Math.max(0, 1 - distinct));\n }\n\n // Plain die: P(all distinct) = falling_factorial(faces, dice) / faces^dice.\n let pDistinct = 1;\n for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces;\n return 1 - pDistinct;\n}\n\n/**\n * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring\n * Elemental Adept and Empowered Spell. Returns a probability in [0, 1].\n *\n * @param diceCount Number of dice rolled.\n * @param dieFaces Faces per die (e.g. 8 for d8).\n * @param options Optional metamagic / feat modifiers.\n */\nexport function calculateBounceOdds(\n diceCount: number,\n dieFaces: number,\n options?: BounceOddsOptions\n): number {\n if (diceCount <= 1) return 0;\n if (diceCount > dieFaces) return 1; // pigeonhole\n\n const minimumDieRoll = options?.minimumDieRoll ?? 0;\n const rerollDamageDice = options?.rerollDamageDice ?? 0;\n\n const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll);\n\n // Without Empowered Spell we're done.\n const rerollCount = Math.min(rerollDamageDice, diceCount);\n if (rerollCount <= 0) return pMatchFirst;\n\n // Empowered Spell: reroll `rerollCount` non-matching dice once. Model the\n // second chance as (a rerolled die matching one of the kept dice) OR (the\n // rerolled dice matching among themselves).\n const pNoMatchFirst = 1 - pMatchFirst;\n const keptDice = diceCount - rerollCount;\n const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces;\n\n // With no kept dice, a rerolled die vacuously \"misses\" all of them (prob 1), so\n // the only way to match is among the rerolled dice themselves (pRerolledMatch below).\n const pRerollDieMissesAll =\n keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1;\n const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll;\n const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0;\n const pMatchAfterReroll = Math.min(\n 1,\n pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches)\n );\n\n return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll);\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n"]} \ No newline at end of file diff --git a/dist/index.d.cts b/dist/index.d.cts new file mode 100644 index 0000000..2ab6b71 --- /dev/null +++ b/dist/index.d.cts @@ -0,0 +1,111 @@ +import { P as PMF } from './pmf-D5VRghZI.cjs'; +export { A as ALL_OUTCOME_TYPES, B as Bin, C as CritConfig, D as DamageDistribution, l as DiceQuery, E as EPS, L as LRUCache, M as MISS_NONE_OUTCOME, d as OUTCOME_DISPLAY_ORDER, O as OutcomeLabelMap, m as OutcomeSnapshot, a as OutcomeType, b as RollType, R as Rounding, S as Snapshot, c as critProbability, o as onAnyHit, e as onCritOnly, f as onHitOnly, h as onMissDamageOnly, g as onMissOnly, k as onPotentCantripOnly, j as onSaveFailOnly, i as onSaveHalfOnly, p as pmfCache, s as sortOutcomes } from './pmf-D5VRghZI.cjs'; + +/** + * Bounce odds — the "birthday problem" for bouncing damage dice (e.g. Chromatic + * Orb): the probability that at least two of K dice with S faces show the same + * value, which is what lets the spell jump to another target. + * + * Accounts for two modifiers: + * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are + * bumped up to it, collapsing the low faces onto a single heavier value. + * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be + * rerolled once, giving a second chance at a match. + * + * The base and Elemental-Adept cases are computed exactly (see + * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered + * on the exact base match probability. + */ +/** Options that modify bounce odds via metamagic / feats. */ +interface BounceOddsOptions { + /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */ + minimumDieRoll?: number; + /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */ + rerollDamageDice?: number; +} +/** + * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring + * Elemental Adept and Empowered Spell. Returns a probability in [0, 1]. + * + * @param diceCount Number of dice rolled. + * @param dieFaces Faces per die (e.g. 8 for d8). + * @param options Optional metamagic / feat modifiers. + */ +declare function calculateBounceOdds(diceCount: number, dieFaces: number, options?: BounceOddsOptions): number; + +/** + * Error thrown when a dice expression cannot be parsed. + * + * Extends the built-in {@link Error}, so existing `catch (e)` / message checks + * continue to work, while callers can now narrow with `instanceof DiceParseError`. + * + * @example + * try { + * parse("d6@3"); + * } catch (e) { + * if (e instanceof DiceParseError) { + * // e.expression === "d6@3" + * } + * } + */ +declare class DiceParseError extends Error { + /** The original expression that failed to parse, when available. */ + readonly expression?: string; + /** The underlying error that triggered this one, when available. */ + readonly cause?: unknown; + constructor(message: string, options?: { + expression?: string; + cause?: unknown; + }); +} + +/** Enable or disable the internal parse cache. */ +declare function setCachingEnabled(enabled: boolean): void; +/** Returns whether the internal parse cache is currently enabled. */ +declare function getCachingEnabled(): boolean; +/** Clears the internal parse cache. */ +declare function clearParserCache(): void; +/** + * Parse a dice expression into a PMF. + * + * - Expression is case-insensitive and ignores spaces. + */ +declare function parse(expression: string, n?: number): PMF; + +/** A labeled mixture builder that preserves provenance in Bin.count. */ +declare class Mixture { + private readonly totals; + private readonly labelMass; + private readonly eps; + constructor(eps?: number); + /** Remove all accumulated state. */ + clear(): this; + /** Number of distinct outcome values currently accumulated. */ + size(): number; + /** Whether a label was ever added. */ + hasLabel(label: L): boolean; + /** + * Add a labeled component with a mixture weight. + * Weight can be any positive finite number. Very small contributions are pruned by eps. + */ + add(label: L, pmf: PMF, weight?: number): this; + buildPMF(eps?: number): PMF; + /** + * Produce normalized *per-label* PMFs (labels independent). + * These are unlabeled PMFs built from the raw mass of that label alone. + */ + byOutcome(): Record; + /** + * Mixture weights per label, normalized to sum to 1 over labels that appeared. + * Uses raw mass before per-outcome normalization. + */ + weights(): Record; + toJSON(): { + totals: Array<[number, number]>; + labels: Array<[number, Record]>; + eps: number; + }; + static mix(items: Array<[label: L, pmf: PMF, weight: number]>, eps?: number): PMF; +} + +export { type BounceOddsOptions, DiceParseError, Mixture, PMF, calculateBounceOdds, clearParserCache, getCachingEnabled, parse, setCachingEnabled }; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..0088156 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,111 @@ +import { P as PMF } from './pmf-D5VRghZI.js'; +export { A as ALL_OUTCOME_TYPES, B as Bin, C as CritConfig, D as DamageDistribution, l as DiceQuery, E as EPS, L as LRUCache, M as MISS_NONE_OUTCOME, d as OUTCOME_DISPLAY_ORDER, O as OutcomeLabelMap, m as OutcomeSnapshot, a as OutcomeType, b as RollType, R as Rounding, S as Snapshot, c as critProbability, o as onAnyHit, e as onCritOnly, f as onHitOnly, h as onMissDamageOnly, g as onMissOnly, k as onPotentCantripOnly, j as onSaveFailOnly, i as onSaveHalfOnly, p as pmfCache, s as sortOutcomes } from './pmf-D5VRghZI.js'; + +/** + * Bounce odds — the "birthday problem" for bouncing damage dice (e.g. Chromatic + * Orb): the probability that at least two of K dice with S faces show the same + * value, which is what lets the spell jump to another target. + * + * Accounts for two modifiers: + * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are + * bumped up to it, collapsing the low faces onto a single heavier value. + * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be + * rerolled once, giving a second chance at a match. + * + * The base and Elemental-Adept cases are computed exactly (see + * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered + * on the exact base match probability. + */ +/** Options that modify bounce odds via metamagic / feats. */ +interface BounceOddsOptions { + /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */ + minimumDieRoll?: number; + /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */ + rerollDamageDice?: number; +} +/** + * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring + * Elemental Adept and Empowered Spell. Returns a probability in [0, 1]. + * + * @param diceCount Number of dice rolled. + * @param dieFaces Faces per die (e.g. 8 for d8). + * @param options Optional metamagic / feat modifiers. + */ +declare function calculateBounceOdds(diceCount: number, dieFaces: number, options?: BounceOddsOptions): number; + +/** + * Error thrown when a dice expression cannot be parsed. + * + * Extends the built-in {@link Error}, so existing `catch (e)` / message checks + * continue to work, while callers can now narrow with `instanceof DiceParseError`. + * + * @example + * try { + * parse("d6@3"); + * } catch (e) { + * if (e instanceof DiceParseError) { + * // e.expression === "d6@3" + * } + * } + */ +declare class DiceParseError extends Error { + /** The original expression that failed to parse, when available. */ + readonly expression?: string; + /** The underlying error that triggered this one, when available. */ + readonly cause?: unknown; + constructor(message: string, options?: { + expression?: string; + cause?: unknown; + }); +} + +/** Enable or disable the internal parse cache. */ +declare function setCachingEnabled(enabled: boolean): void; +/** Returns whether the internal parse cache is currently enabled. */ +declare function getCachingEnabled(): boolean; +/** Clears the internal parse cache. */ +declare function clearParserCache(): void; +/** + * Parse a dice expression into a PMF. + * + * - Expression is case-insensitive and ignores spaces. + */ +declare function parse(expression: string, n?: number): PMF; + +/** A labeled mixture builder that preserves provenance in Bin.count. */ +declare class Mixture { + private readonly totals; + private readonly labelMass; + private readonly eps; + constructor(eps?: number); + /** Remove all accumulated state. */ + clear(): this; + /** Number of distinct outcome values currently accumulated. */ + size(): number; + /** Whether a label was ever added. */ + hasLabel(label: L): boolean; + /** + * Add a labeled component with a mixture weight. + * Weight can be any positive finite number. Very small contributions are pruned by eps. + */ + add(label: L, pmf: PMF, weight?: number): this; + buildPMF(eps?: number): PMF; + /** + * Produce normalized *per-label* PMFs (labels independent). + * These are unlabeled PMFs built from the raw mass of that label alone. + */ + byOutcome(): Record; + /** + * Mixture weights per label, normalized to sum to 1 over labels that appeared. + * Uses raw mass before per-outcome normalization. + */ + weights(): Record; + toJSON(): { + totals: Array<[number, number]>; + labels: Array<[number, Record]>; + eps: number; + }; + static mix(items: Array<[label: L, pmf: PMF, weight: number]>, eps?: number): PMF; +} + +export { type BounceOddsOptions, DiceParseError, Mixture, PMF, calculateBounceOdds, clearParserCache, getCachingEnabled, parse, setCachingEnabled }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..c6f02d8 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,3601 @@ +// src/common/bounce.ts +function binom(n, k) { + if (k < 0 || k > n) return 0; + let result = 1; + for (let i = 0; i < k; i++) result = result * (n - i) / (i + 1); + return result; +} +function pAllDistinct(dice, faces, uniformCount, heavyWeight) { + const light = 1 / faces; + const eK = binom(uniformCount, dice) * Math.pow(light, dice) + heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1); + let kFactorial = 1; + for (let i = 2; i <= dice; i++) kFactorial *= i; + return kFactorial * eK; +} +function pMatch(dice, faces, minimumDieRoll) { + if (dice <= 1) return 0; + if (dice > faces) return 1; + if (minimumDieRoll >= 2) { + const uniformCount = faces - minimumDieRoll; + const effectiveValues = uniformCount + 1; + if (dice > effectiveValues) return 1; + const heavyWeight = minimumDieRoll / faces; + const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight); + return Math.min(1, Math.max(0, 1 - distinct)); + } + let pDistinct = 1; + for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces; + return 1 - pDistinct; +} +function calculateBounceOdds(diceCount, dieFaces, options) { + if (diceCount <= 1) return 0; + if (diceCount > dieFaces) return 1; + const minimumDieRoll = options?.minimumDieRoll ?? 0; + const rerollDamageDice = options?.rerollDamageDice ?? 0; + const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll); + const rerollCount = Math.min(rerollDamageDice, diceCount); + if (rerollCount <= 0) return pMatchFirst; + const pNoMatchFirst = 1 - pMatchFirst; + const keptDice = diceCount - rerollCount; + const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces; + const pRerollDieMissesAll = keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1; + const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll; + const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0; + const pMatchAfterReroll = Math.min( + 1, + pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches) + ); + return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll); +} + +// src/common/errors.ts +var DiceParseError = class _DiceParseError extends Error { + constructor(message, options) { + super(message); + this.name = "DiceParseError"; + this.expression = options?.expression; + this.cause = options?.cause; + Object.setPrototypeOf(this, _DiceParseError.prototype); + } +}; + +// src/common/lru-cache.ts +var LRUCache = class { + constructor(maxSize = 1e3) { + this.maxSize = maxSize; + this.cache = /* @__PURE__ */ new Map(); + } + get(key) { + const value = this.cache.get(key); + if (value === void 0) return void 0; + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + delete(key) { + this.cache.delete(key); + } + set(key, value) { + if (this.cache.size >= this.maxSize && !this.cache.has(key)) { + const oldestKey = this.cache.keys().next().value; + this.cache.delete(oldestKey); + } + this.cache.delete(key); + this.cache.set(key, value); + return this; + } + clear() { + this.cache.clear(); + } + get size() { + return this.cache.size; + } + has(key) { + return this.cache.has(key); + } + keys() { + return this.cache.keys(); + } + values() { + return this.cache.values(); + } +}; + +// src/common/types.ts +var EPS = 1e-12; +function critProbability(critRange, rollType = "flat") { + const base = critRange / 20; + switch (rollType) { + case "advantage": + return 1 - (1 - base) ** 2; + case "elven accuracy": + return 1 - (1 - base) ** 3; + case "disadvantage": + return base ** 2; + case "flat": + default: + return base; + } +} +var MISS_NONE_OUTCOME = "missNone"; +var ALL_OUTCOME_TYPES = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" +]; +var OUTCOME_DISPLAY_ORDER = [ + "crit", + "hit", + "missDamage", + "saveHalf", + "saveFail", + "pc", + "missNone" +]; +function sortOutcomes(outcomes, order = ALL_OUTCOME_TYPES) { + const rank = new Map(order.map((o, i) => [o, i])); + return [...outcomes].sort((a, b) => { + const ra = rank.get(a); + const rb = rank.get(b); + if (ra !== void 0 && rb !== void 0) return ra - rb; + if (ra !== void 0) return -1; + if (rb !== void 0) return 1; + return a.localeCompare(b); + }); +} +var onAnyHit = ["hit", "crit"]; +var onCritOnly = ["crit"]; +var onHitOnly = ["hit"]; +var onMissOnly = ["missNone", "missDamage"]; +var onMissDamageOnly = ["missDamage"]; +var onSaveHalfOnly = ["saveHalf"]; +var onSaveFailOnly = ["saveFail"]; +var onPotentCantripOnly = ["pc"]; + +// src/pmf/query.ts +var _DiceQuery = class _DiceQuery { + constructor(singles, combined, eps = EPS) { + this.singles = Array.isArray(singles) ? singles : [singles]; + if (this.singles.some((s) => s === void 0)) { + throw new Error("DiceQuery contains undefined singles"); + } + this._eps = eps; + this._combinedProvided = combined !== void 0; + if (combined !== void 0) { + this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); + } + } + /** + * The combined damage distribution of all single PMFs (their convolution), + * normalized to total probability 1. + * + * Computed lazily on first access and cached. Queries that only need + * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, + * {@link DiceQuery.stddev} — never trigger this convolution. + */ + get combined() { + if (this._combined === void 0) { + const c = PMF.convolveMany(this.singles); + this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); + } + return this._combined; + } + /** + * Returns a new PMF with damage attribution metadata populated. + * + * This method computes attribution on-demand for builder-generated PMFs, + * enabling them to work with damage attribution charts. The `attr` field + * tracks how much damage each outcome type contributes at each damage value. + * + * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) + * + * Performance: Cached after first call. Adds minimal overhead vs `combined`. + * + * @returns PMF with attr field populated for damage attribution charts + * + * @example + * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) + * const query = attack.toQuery() + * const pmf = query.combinedWithAttribution() + * // Now pmf can be used with toDamageAttributionChartSeries() + */ + combinedWithAttribution() { + if (this._combinedWithAttr) { + return this._combinedWithAttr; + } + if (this.singles.every((pmf) => pmf.hasAttribution())) { + this._combinedWithAttr = this.combined; + return this._combinedWithAttr; + } + const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); + const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); + const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); + this._combinedWithAttr = normalized; + return normalized; + } + /** + * Per-label `damage value → probability mass` series for the combined, + * attribution-carrying distribution — the provenance core of the stacked + * damage-attribution chart. Convenience for + * `combinedWithAttribution().attributionByValue()`; see + * {@link PMF.attributionByValue}. + */ + attributionByValue() { + return this.combinedWithAttribution().attributionByValue(); + } + /** + * How many of the independent single PMFs can produce the given outcome + * label. Useful for "all of them succeeded" style probabilities where the + * exponent is the number of contributing attacks (see + * {@link DiceQuery.probExactlyK}). + */ + countSinglesWith(label) { + let count = 0; + for (const single of this.singles) { + if (single.hasOutcome(label)) count++; + } + return count; + } + /** + * Returns the expected damage across all possible outcomes. + * + * Example: `query.mean()` → 12.5 + * Use case: "What's my average damage per round?" + */ + mean() { + if (this._combinedProvided) { + let m = 0; + for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; + return m; + } + let totalMean = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; + } + return totalMean; + } + /** + * Returns the variance of the damage distribution. + * + * Example: `query.variance()` → 45.2 + * Use case: "How much does my damage vary from the average?" + * High variance means higher risk/reward. Lower variance means more consistent damage. + */ + variance() { + if (this._combinedProvided) { + const mu = this.mean(); + let v = 0; + for (const [damageValue, bin] of this.combined) { + const dev = damageValue - mu; + v += dev * dev * bin.p; + } + return v; + } + let totalVariance = 0; + for (const single of this.singles) { + const mass = single.mass(); + if (mass <= 0) continue; + if (Math.abs(mass - 1) <= this._eps) { + totalVariance += single.variance(); + } else { + let mu = 0; + for (const [d, b] of single) mu += d * (b.p / mass); + let v = 0; + for (const [d, b] of single) { + const dev = d - mu; + v += dev * dev * (b.p / mass); + } + totalVariance += v; + } + } + return totalVariance; + } + /** + * Returns the standard deviation of the damage distribution. + * + * Example: `query.stdev()` → 6.7 + * Use case: "What's the typical spread around my average damage?" + * Used to determine how consistent the damage is. + */ + stddev() { + return Math.sqrt(this.variance()); + } + /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ + stdev() { + return this.stddev(); + } + /** + * Returns the Cumulative Distribution Function. + */ + cdf(x) { + return this.probTotalAtMost(x); + } + /** + * Returns the probability of dealing X damage or less. + * In statistics, this is called the cumulative distribution function (CDF). + * Example: `query.cdf(20)` → 0.75 + * Use case: "What's the chance I deal 20 damage or less?" + */ + probTotalAtMost(x) { + let cumulativeProbability = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue <= x) { + cumulativeProbability += probabilityBin.p; + } + } + return cumulativeProbability; + } + /** + * Returns the Complementary Cumulative Distribution Function. + */ + ccdf(x) { + return this.probTotalAtLeast(x); + } + /** + * Returns the probability of dealing at least X damage. + * + * Example: `query.probTotalAtLeast(25)` → 0.35 + * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" + */ + probTotalAtLeast(threshold) { + let probabilitySum = 0; + for (const [damageValue, probabilityBin] of this.combined) { + if (damageValue >= threshold) { + probabilitySum += probabilityBin.p; + } + } + return probabilitySum; + } + /** + * Returns damage values at specific percentiles. + * + * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] + * Use case: "What are my 25th, 50th, and 75th percentile damage values?" + */ + percentiles(percentileValues) { + const sortedDamageValues = this.combined.support(); + if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); + const cumulativeProbabilities = []; + let runningProbabilitySum = 0; + for (const damageValue of sortedDamageValues) { + runningProbabilitySum += this.combined.map.get(damageValue).p; + cumulativeProbabilities.push(runningProbabilitySum); + } + return percentileValues.map((targetPercentile) => { + let leftBound = 0; + let rightBound = cumulativeProbabilities.length - 1; + while (leftBound <= rightBound) { + const middleIndex = Math.floor((leftBound + rightBound) / 2); + if (cumulativeProbabilities[middleIndex] >= targetPercentile) { + rightBound = middleIndex - 1; + } else { + leftBound = middleIndex + 1; + } + } + return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; + }); + } + /** + * Returns the minimum possible damage. + * + * Example: `query.min()` → 0 + * Use case: "What's the worst-case damage if everything misses?" + */ + min() { + return this.combined.min(); + } + /** + * Returns the maximum possible damage. + * + * Example: `query.max()` → 56 + * Use case: "What's the best-case damage if everything crits and rolls max?" + */ + max() { + return this.combined.max(); + } + singleProb(diceIndex, label) { + const single = this.singles[diceIndex]; + let probabilitySum = 0; + for (const [, probabilityBin] of single) { + probabilitySum += probabilityBin.count[label] || 0; + } + const mass = single.mass(); + return mass > 0 ? probabilitySum / mass : 0; + } + /** + * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it + * carries ANY of `labels`", over the n independent singles. + * + * Each single's per-event success probability is the Poisson-binomial + * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), + * computed exactly once. The binomial DP then runs once to produce the whole + * distribution, so the array-label paths of probExactlyK / probAtLeastK / + * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and + * re-running the DP per requested k. + */ + countDistribution(labels) { + const n = this.singles.length; + const successProbabilities = this.singles.map( + (single) => new _DiceQuery([single]).probabilityOf(labels) + ); + const dist = new Array(n + 1).fill(0); + dist[0] = 1; + for (const successProb of successProbabilities) { + for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { + dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; + } + dist[0] *= 1 - successProb; + } + return dist; + } + probAtLeastK(labels, k) { + const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; + const n = this.singles.length; + if (k <= 0) return 1; + if (k > n) return 0; + const dist = this.countDistribution(L); + let tail = 0; + for (let i = k; i <= n; i++) { + tail += dist[i]; + } + if (tail < 0) return 0; + if (tail > 1) return 1; + return tail; + } + /** + * Returns the probability that at least one attack has the specified outcome(s). + * - This is the complement of probAtMostK(labels, 0) + * + * Examples: + * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) + * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) + * + * Use cases: + * - "What's the chance at least one of my attacks connects?" + * + * Note: + * + * - You have to pass in an array of labels to avoid double-counting if you are + * using multiple labels. You cannot just add them. + */ + probAtLeastOne(labels) { + if (typeof labels === "string") { + labels = [labels]; + } + let productOfNonOccurrence = 1; + for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { + let combinedProbability = 0; + for (const label of labels) { + combinedProbability += this.singleProb(diceIndex, label); + } + if (combinedProbability < 0) combinedProbability = 0; + else if (combinedProbability > 1) combinedProbability = 1; + productOfNonOccurrence *= 1 - combinedProbability; + } + const result = 1 - productOfNonOccurrence; + return result < 0 ? 0 : result > 1 ? 1 : result; + } + /** + * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. + * + * Uses dynamic programming to efficiently calculate the probability distribution + * of how many attacks will have the specified outcome, accounting for different + * success probabilities across individual attacks. + * + * Example: For 3 attacks with 50% hit chance each, returns: + * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] + * + * @param label - The outcome type to count + * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) + * @returns Array where index K contains P(exactly K attacks have the label) + */ + computeBinomialProbabilities(label, maxK) { + const individualProbabilities = this.singles.map( + (_, diceIndex) => this.singleProb(diceIndex, label) + ); + const binomialProbs = new Array(maxK + 1).fill(0); + binomialProbs[0] = 1; + for (const singleProbability of individualProbabilities) { + for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { + binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; + } + binomialProbs[0] *= 1 - singleProbability; + } + return binomialProbs; + } + /** + * Returns the probability that exactly K attacks result in the specified outcome(s). + * + * Single label examples: + * - probExactlyK('hit', 2) = probability exactly 2 attacks hit + * - probExactlyK('crit', 1) = probability exactly 1 attack crits + * - probExactlyK('crit', 0) = probability no attacks crit + * + * Array examples: + * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed + * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds + * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss + * + * Use cases: + * - "What's the chance exactly one of my attacks hits?" + * - "How likely am I to get exactly 2 successes out of 3 attacks?" + * - "What's the probability that exactly half my attacks succeed?" + * + * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. + * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. + */ + probExactlyK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + return probabilityArray[k]; + } + const dist = this.countDistribution(labels); + return k >= 0 && k < dist.length ? dist[k] : 0; + } + /** + * Returns the probability that AT MOST K attacks result in the specified outcome(s). + * + * Single label examples: + * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) + * - probAtMostK('crit', 0) = probability no attacks crit + * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss + * + * Array examples: + * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds + * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) + * + * Use cases: + * - "What's the chance that at most one attack hits?" (rest miss) + * - "How likely am I to have mostly failures?" (at most 1 success) + * - "What's the probability of a really bad turn?" (at most 0 successes) + * + */ + probAtMostK(labels, k) { + if (typeof labels === "string") { + const probabilityArray = this.computeBinomialProbabilities(labels, k); + let cumulativeSum2 = 0; + for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { + cumulativeSum2 += probabilityArray[outcomeCount]; + } + return cumulativeSum2; + } + const dist = this.countDistribution(labels); + const upper = Math.min(k, dist.length - 1); + let cumulativeSum = 0; + for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { + cumulativeSum += dist[outcomeCount]; + } + return cumulativeSum; + } + /** + * Returns the expected damage attributed to specific outcome types. + * + * Single label examples: + * - expectedDamageFrom('hit') = expected damage from hit components + * - expectedDamageFrom('crit') = expected damage from crit components + * + * Array examples: + * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success + * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses + * + * Use cases: + * - "How much damage do I expect from successful attacks?" + * - "What's the damage contribution from critical hits specifically?" + * - "How much damage comes from miss effects (like save-for-half spells)?" + */ + expectedDamageFrom(labels) { + const wanted = Array.isArray(labels) ? labels : [labels]; + let total = 0; + for (const single of this.singles) { + for (const [dmg, bin] of single) { + let p = 0; + for (const label of wanted) p += bin.count[label] ?? 0; + total += dmg * p; + } + } + return total; + } + /** + * Returns damage statistics for scenarios where AT LEAST ONE attack results in + * the specified outcome(s). + * + * This method answers "What happens when things go reasonably well?" rather than + * "What's the theoretical maximum?" It includes mixed scenarios which are more + * common and tactically relevant than pure scenarios. + * + * Single label examples: + * - damageStatsFrom('hit') = damage range when at least one attack hits + * - damageStatsFrom('crit') = damage range when at least one attack crits + * + * Array examples: + * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds + * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses + * + * Tactical Use Cases: + * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" + * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" + * - "Should I use this risky spell if it has good damage when it works?" + * - "What's my damage potential when something goes right?" (vs pure failure) + * + * Combat Planning Examples: + * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" + * (Much more useful than "You average 50 damage including complete misses") + * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" + * - Resource management: "If I hit anything, I'll likely finish this enemy" + * + * Statistical Note: + * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which + * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. + * + * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an + * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and + * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than + * E[dmg | the label occurs]. For a single attack both are the plain + * conditional figures. Use {@link probAtLeastOne} for the scenario probability. + * + * @example + * // High-level tactical planning + * const successStats = query.damageStatsFrom('hit') + * const successChance = query.probAtLeastOne('hit') + * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) + */ + damageStatsFrom(labels) { + const labelArray = typeof labels === "string" ? [labels] : labels; + let minDamage = Infinity; + let maxDamage = -Infinity; + let totalDamage = 0; + let totalCount = 0; + for (const [damage, probabilityBin] of this.combined) { + let binHasAnyLabel = false; + let binContribution = 0; + for (const label of labelArray) { + const count = probabilityBin.count[label]; + if (count && count > 0) { + binHasAnyLabel = true; + binContribution += count; + } + } + if (damage > 0 && binHasAnyLabel) { + minDamage = Math.min(minDamage, damage); + maxDamage = Math.max(maxDamage, damage); + const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; + totalDamage += damage * weightToUse; + totalCount += weightToUse; + } + } + return { + min: minDamage === Infinity ? 0 : minDamage, + max: maxDamage === -Infinity ? 0 : maxDamage, + avg: totalCount > 0 ? totalDamage / totalCount : 0, + count: totalCount + }; + } + /** + * Returns damage statistics for scenarios where ALL attacks result in the specified + * outcome, calculated by leveraging the pure partition of singles. + * + * This method answers "What's the theoretical best/worst case?" and "What are the + * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. + * + * Examples: + * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) + * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) + * + * UI and Display Use Cases: + * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) + * - "Best case scenario" vs "worst case scenario" analysis + * - Mathematical verification: "Does our hit damage calculation match manual math?" + * - Clean damage type attribution: "How much comes from base hits vs crits?" + * + * Design and Balance Use Cases: + * - Game designers: "What's the damage ceiling if someone gets lucky?" + * - Character optimization: "What's my absolute maximum potential?" + * - Ability comparison: "Which build has higher crit ceiling?" + * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" + * + * Mathematical Use Cases: + * - Validating complex calculations against simple manual math + * - Understanding damage component contributions in isolation + * - Separating luck (crit variance) from consistency (hit variance) + * - Building intuition about damage sources + * + * When to Use This vs damageStatsFrom(): + * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons + * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios + * + * Statistical Note: + * Pure scenarios (all hits, all crits) are rare but represent clear mathematical + * boundaries. These stats help understand the "shape" of your damage potential. + * + * @example + * // UI display logic + * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" + * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" + * + * // vs tactical planning (use damageStatsFrom instead) + * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios + */ + combinedDamageStats(targetLabel) { + const singleStats = this.singles.map( + (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) + ); + if (singleStats.some((stats) => stats.count === 0)) { + return { min: 0, max: 0, avg: 0, count: 0 }; + } + const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); + const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); + const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); + const combinedProb = singleStats.reduce( + (product, stats) => product * stats.count, + 1 + ); + return { + min: combinedMin, + max: combinedMax, + avg: combinedAvg, + count: combinedProb + }; + } + /** + * Returns the probability that at least one attack carries ANY of the + * specified labels (the marginal P(≥1) across the independent attacks). + * + * Examples: + * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) + * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) + * + * Use cases: + * - "What's the chance my resolution includes a success label?" + * - "How likely am I to get any hits or crits across all attacks?" + * + * Note: this must NOT be computed by summing `combined` bin probabilities. A + * single combined damage total is reachable by many outcome combinations and + * a bin can hold several labels at once, so summing `bin.p` over bins that + * contain a label over-counts. The correct marginal is the Poisson-binomial + * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. + */ + probabilityOf(labels) { + return this.probAtLeastOne(labels); + } + /** + * Returns the probability of missing (any type of miss). + * + * Example: `query.missChance()` → 0.04 + * Use case: "What's the chance I miss completely this turn?" + */ + missChance() { + return this.probabilityOf(["missDamage", "missNone"]); + } + /** + * Returns data formatted for plotting damage probability distribution. + * + * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] + * Use case: "I want to visualize my damage distribution in a chart." + */ + toChartSeries() { + return this.combined.support().map((damageValue) => ({ + x: damageValue, + y: this.combined.map.get(damageValue).p + })); + } + /** + * Returns tabular data showing damage values and their probability breakdowns. + * + * Example: `query.toLabeledTable(['hit', 'crit'])` → + * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] + * + * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." + */ + toLabeledTable(labels = []) { + return this.combined.support().map((damageValue) => { + const probabilityBin = this.combined.map.get(damageValue); + const tableRow = { + damage: damageValue, + total: probabilityBin.p + }; + for (const outcomeLabel of labels) { + tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; + } + return tableRow; + }); + } + /** + * Returns data for stacked charts with unconditional per-label probability mass per damage. + * + * - Each dataset value equals the unconditional probability mass for that label at that damage + * (i.e., `bin.count[label]`). + * - Column sums may be less than the total probability `bin.p` when you omit labels or when + * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. + * - This behavior matches tests that expect raw per-label mass (not proportional scaling). + * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. + * + * @example + * query.toStackedChartData(['hit', 'crit']) + * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} + */ + toStackedChartData(labels = [], epsilon = EPS) { + const damageValues = this.combined.support(); + const datasets = labels.map((outcomeLabel) => ({ + label: outcomeLabel, + data: damageValues.map((dmg) => { + const bin = this.combined.map.get(dmg); + const v = bin ? bin.count[outcomeLabel] || 0 : 0; + return v <= epsilon ? 0 : v; + }) + })); + return { labels: damageValues, datasets }; + } + /** + * Returns pure mathematical data for attribution charts showing outcome contributions. + * + * Automatically discovers all outcome types present in the PMF, applies filtering rules, + * and returns proportional data suitable for stacked visualization. + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and proportional data + * + * @example + * query.toAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} + */ + toAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeCount = bin.count[outcome] || 0; + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + const outcomeProbability = bin.p * outcomeFraction; + return asPercentages ? outcomeProbability * 100 : outcomeProbability; + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for damage attribution charts showing damage contribution + * from each outcome type at each damage value. + * + * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of + * bin.count (probability attribution). + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) + * @returns Pure data structure with support, outcomes, and damage attribution data + * + * @example + * query.toDamageAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} + */ + toDamageAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + if (bin.attr) { + for (const outcomeType in bin.attr) { + if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin || !bin.attr) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + const outcomeDamageAttribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { + if (filterRules(outcomeName, damage)) { + totalDamageAttribution += damageAttr || 0; + } + } + if (totalDamageAttribution === 0) return 0; + const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; + return damagePercentage * bin.p * 100; + } else { + return outcomeDamageAttribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for outcome attribution charts showing which + * attack outcome combinations can produce each damage value. + * + * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks + * outcome combinations - answering "what attack outcomes produced this damage?" + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and outcome combination probabilities + * + * @example + * query.toOutcomeAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} + */ + toOutcomeAttributionChartSeries(options = {}) { + const { + stackOrder = [ + "missNone", + "missDamage", + "saveFail", + "saveHalf", + "pc", + "hit", + "crit" + ], + filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), + asPercentages = true + } = options; + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], outcomes: [], data: {} }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + const allOutcomeTypes = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const outcomeType in bin.count) { + if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { + allOutcomeTypes.add(outcomeType); + } + } + } + const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { + const indexA = stackOrder.indexOf(a); + const indexB = stackOrder.indexOf(b); + if (indexA >= 0 && indexB >= 0) return indexA - indexB; + if (indexA >= 0) return -1; + if (indexB >= 0) return 1; + return a.localeCompare(b); + }); + if (existingOutcomes.length === 0) { + return { support, outcomes: [], data: {} }; + } + const data = {}; + for (const outcome of existingOutcomes) { + data[outcome] = support.map((damage) => { + const bin = this.combined.map.get(damage); + if (!bin) return 0; + if (!filterRules(outcome, damage)) { + return 0; + } + if (outcome === "missNone") { + const outcomeCount = bin.count[outcome] || 0; + if (outcomeCount === 0) return 0; + if (asPercentages) { + let totalChartableCount = 0; + for (const [outcomeName, count] of Object.entries(bin.count)) { + if (filterRules(outcomeName, damage)) { + totalChartableCount += count || 0; + } + } + if (totalChartableCount === 0) return 0; + const outcomeFraction = outcomeCount / totalChartableCount; + return outcomeFraction * bin.p * 100; + } else { + return outcomeCount; + } + } + if (!bin.attr) return 0; + const outcomeDamageContribution = bin.attr[outcome] || 0; + if (asPercentages) { + let totalDamageAttribution = 0; + for (const [, damageAttr] of Object.entries(bin.attr)) { + totalDamageAttribution += damageAttr || 0; + } + if (totalDamageAttribution === 0) return 0; + const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; + return outcomeFraction * bin.p * 100; + } else { + return outcomeDamageContribution; + } + }); + } + return { + support, + outcomes: existingOutcomes, + data + }; + } + /** + * Returns pure mathematical data for cumulative distribution function (CDF). + * Shows P(X ≤ x) - the probability of getting at most x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and cumulative probabilities + * + * @example + * query.toCDFSeries() + * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} + */ + toCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const cdfData = []; + for (const damage of support) { + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + cdfData.push( + asPercentages ? cumulativeProbability * 100 : cumulativeProbability + ); + } + return { + support, + data: cdfData + }; + } + /** + * Returns pure mathematical data for complementary cumulative distribution function (CCDF). + * Shows P(X ≥ x) - the probability of getting at least x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and complementary cumulative probabilities + * + * @example + * query.toCCDFSeries() + * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} + */ + toCCDFSeries(asPercentages = true) { + const originalSupport = this.combined.support(); + if (originalSupport.length === 0) { + return { support: [], data: [] }; + } + const minDamage = Math.min(...originalSupport); + const maxDamage = Math.max(...originalSupport); + const support = Array.from( + { length: maxDamage - minDamage + 1 }, + (_, i) => minDamage + i + ); + let cumulativeProbability = 0; + const ccdfData = []; + for (const damage of support) { + const ccdf = 1 - cumulativeProbability; + ccdfData.push(asPercentages ? ccdf * 100 : ccdf); + const bin = this.combined.map.get(damage); + if (bin) { + cumulativeProbability += bin.p; + } + } + return { + support, + data: ccdfData + }; + } + /* + Statistics snapshot of the query. + */ + /** Probability of doing strictly more than threshold damage (default >0). */ + probDamageGreaterThan(threshold = 0) { + let acc = 0; + for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; + return acc; + } + /** All outcome keys actually present (typed & ordered if you pass an order). */ + outcomeKeys(order) { + const found = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) + if (bin.count[k] && bin.count[k] > 0) found.add(k); + } + if (found.size === 0) + ["hit", "crit", "missNone"].forEach((k) => found.add(k)); + const keys = Array.from(found).filter( + (k) => order?.includes(k) ?? true + ); + if (order && order.length) + keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); + return keys; + } + /** Total probability per outcome across the PMF. */ + outcomeTotals(outcomes = this.outcomeKeys()) { + const totals = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => totals.set(o, 0)); + for (const [, row] of this.combined.map) { + for (const o of outcomes) { + const p = row.count[o] || 0; + totals.set(o, (totals.get(o) || 0) + p); + } + } + return totals; + } + /** Conditional damage range per outcome (min/avg/max of X | outcome). */ + outcomeDamageRanges(outcomes = this.outcomeKeys()) { + const table = this.toLabeledTable(outcomes); + const ranges = /* @__PURE__ */ new Map(); + outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); + for (const row of table) { + const dmg = row.damage; + for (const o of outcomes) { + const p = row[o] || 0; + if (p > 0) { + const r = ranges.get(o); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + } + const out = /* @__PURE__ */ new Map(); + for (const o of outcomes) { + const r = ranges.get(o); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); + } + return out; + } + /** + * Snapshot of the distribution in the exact shape the UI consumes. + * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) + * - damageRange is conditional on the outcome occurring + * + * The outcome probabilities use the correct Poisson-binomial marginals + * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), + * so they are always valid probabilities in [0,1]. + * + * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from + * the combined PMF's `count`, which the convolution accumulates as an EXPECTED + * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] + * rather than a clean conditional expectation. It is correct for a single + * attack. + */ + snapshot(order) { + const discovered = /* @__PURE__ */ new Set(); + for (const [, bin] of this.combined.map) { + for (const k in bin.count) { + if (bin.count[k] && bin.count[k] > 0) discovered.add(k); + } + } + if (discovered.size === 0) { + for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); + } + let outcomes = Array.from(discovered); + if (order && order.length) { + const inOrder = new Set(order); + outcomes = outcomes.filter((k) => inOrder.has(k)); + const rank = new Map(order.map((k, i) => [k, i])); + outcomes.sort( + (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) + ); + } + const rows = this.toLabeledTable(outcomes); + const rangeAcc = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + rangeAcc.set(ot, { sum: 0, mass: 0 }); + } + for (const row of rows) { + const dmg = row.damage; + for (const ot of outcomes) { + const p = row[ot] || 0; + if (p <= 0) continue; + const r = rangeAcc.get(ot); + r.sum += dmg * p; + r.mass += p; + if (r.min === void 0 || dmg < r.min) r.min = dmg; + if (r.max === void 0 || dmg > r.max) r.max = dmg; + } + } + const n = this.singles.length; + const outcomeMap = /* @__PURE__ */ new Map(); + for (const ot of outcomes) { + const r = rangeAcc.get(ot); + const avg = r.mass > 0 ? r.sum / r.mass : 0; + outcomeMap.set(ot, { + atLeastOneProbability: this.probAtLeastOne(ot), + allProbability: this.probAtLeastK(ot, n), + damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } + }); + } + const averageDPR = this.mean(); + let damageChance = 0; + for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; + const { support, data } = this.toCDFSeries(false); + const quantile = (p) => { + if (support.length === 0) return 0; + for (let i = 0; i < support.length; i++) + if (data[i] >= p) return support[i]; + return support[support.length - 1]; + }; + const percentiles = { + p25: quantile(0.25), + p50: quantile(0.5), + p75: quantile(0.75) + }; + return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; + } + /** + * PMF Transformation Methods + * + * These methods provide a fluent API for transforming dice queries by wrapping + * the underlying PMF transformation methods. All operations work on the combined + * PMF and return new DiceQuery instances. + */ + /** + * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). + * + * @returns New DiceQuery with normalized combined PMF + */ + normalize() { + return new _DiceQuery([this.combined.normalize()]); + } + /** + * Returns a new DiceQuery with low-probability outcomes removed. + * + * @param eps Minimum probability threshold (defaults to PMF epsilon) + * @param keepFinalBin Whether to keep the highest damage bin regardless of probability + * @returns New DiceQuery with compacted combined PMF + */ + compact(eps, keepFinalBin) { + return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); + } + /** + * Returns a new DiceQuery with an additional scaled branch added. + * Useful for conditional outcomes like "30% chance of opportunity attack". + * + * @param branch DiceQuery to add as a scaled branch + * @param probability Probability of the branch occurring (0-1) + * @returns New DiceQuery combining this query with the scaled branch + * + * @example + * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); + * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); + */ + addScaled(branch, probability) { + return new _DiceQuery([ + this.combined.addScaled(branch.combined, probability) + ]); + } + /** + * Returns a new DiceQuery with all probabilities scaled by a factor. + * Used for conditional scenarios where the entire outcome has reduced probability. + * + * @param factor Scaling factor for probabilities + * @returns New DiceQuery with scaled probabilities + * + * @example + * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario + */ + scaleMass(factor) { + return new _DiceQuery([this.combined.scaleMass(factor)]); + } + totalMass() { + return this.combined.mass(); + } + /** + * Returns a new DiceQuery with damage values transformed by a function. + * Useful for applying modifiers, resistances, or other damage transformations. + * + * @param damageTransformFunction Function to transform each damage value + * @returns New DiceQuery with transformed damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage + * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage + */ + mapDamage(damageTransformFunction) { + return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); + } + /** + * Returns a new DiceQuery with damage values scaled by a factor. + * Convenient wrapper around mapDamage for multiplicative scaling. + * + * @param factor Scaling factor for damage values + * @param rounding Rounding method: "floor" (default), "round", or "ceil" + * @returns New DiceQuery with scaled damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const doubled = baseAttack.scaleDamage(2); // Double damage + * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded + */ + scaleDamage(factor, rounding = "floor") { + return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); + } + /** + * Returns a new DiceQuery combining this query with another via convolution. + * Equivalent to rolling both queries independently and adding results. + * It is important to use this rather than combing()ing the PMFs directly! + * This method maintains the provenance of the PMFs which is needed for damage attribution. + * Combining the .combined PMFs directly is still valid for DPR calculations but + * is not statistically sound for queries. + * + * @param other DiceQuery to combine with + * @param eps Optional epsilon for precision control + * @returns New DiceQuery representing the combined outcome + * + * @example + * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); + * const bothAttacks = mainAttack.convolve(bonusAttack); + */ + convolve(other) { + const singles = [...this.singles, ...other.singles]; + return new _DiceQuery(singles); + } + /** + * First-success split over an ordered list of DISTINCT single-swing PMFs. + * Each PMF may have different success/subset probabilities (from labels). + * + * successOutcome: e.g., ["success"] or ["hit", "crit"] + * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success + * + * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] + */ + firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { + const pmfs = this.singles; + if (!pmfs.length) { + throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); + } + const toArr = (x) => Array.isArray(x) ? x : [x]; + const clamp01 = (x) => Math.max(0, Math.min(1, x)); + const tol = Math.max(eps, 8 * Number.EPSILON); + const per = pmfs.map((pmf) => { + const dq = new _DiceQuery([pmf]); + const pS = dq.probAtLeastOne(toArr(successOutcome)); + const pB = dq.probAtLeastOne(toArr(subsetOutcome)); + if (pB - pS > eps) { + throw new Error( + "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." + ); + } + return { pS, pB }; + }); + let missSoFar = 1; + let pFirstSubset = 0; + let pFirstNonSubset = 0; + let pNone = 1; + for (const { pS, pB } of per) { + pFirstSubset += missSoFar * pB; + pFirstNonSubset += missSoFar * (pS - pB); + const miss = 1 - pS; + missSoFar *= miss; + pNone *= miss; + } + const pAny = 1 - pNone; + const a = clamp01(pFirstNonSubset); + const b = clamp01(pFirstSubset); + const any = clamp01(pAny); + const none = clamp01(pNone); + if (Math.abs(a + b - any) > tol * Math.max(1, any)) { + throw new Error( + `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` + ); + } + return [a, b, any, none]; + } +}; +_DiceQuery.DEFAULT_OUTCOMES = [ + "hit", + "crit", + "missNone" +]; +var DiceQuery = _DiceQuery; +var pmfCache = new LRUCache(1e3); +var _PMF = class _PMF { + constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { + this.map = map; + this.epsilon = epsilon; + this.normalized = normalized; + this.identifier = identifier; + this._preservedProvenance = _preservedProvenance; + } + static empty(epsilon = EPS, identifier = "empty") { + return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); + } + // This has a single bin at value 0, mass of 1 + static zero(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "zero"); + } + static delta(value, epsilon = EPS) { + return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); + } + /** + * Point mass at damage 0 tagged with the canonical `missNone` outcome. + * + * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the + * builder's attack-resolution vocabulary. This uses the `missNone` + * {@link OutcomeType} that the attribution charts and outcome stats key on, + * so it is the correct "clean miss / no damage" delta for provenance-aware + * mixtures feeding those consumers. + */ + static missNone(epsilon = EPS) { + const m = /* @__PURE__ */ new Map(); + m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); + return new _PMF(m, epsilon, false, "missNone"); + } + // This creates a single bin at value 0, but with weight 0. + static emptyMass() { + return _PMF.zero().scaleMass(0); + } + // Makes PMF iterable over [damage, bin] pairs. + [Symbol.iterator]() { + return this.map[Symbol.iterator](); + } + static clearCache() { + pmfCache.clear(); + } + /** + * Creates a conditional PMF from two branches (success and failure) and a probability. + * This is the core logic for modeling any probabilistic event where there are two + * distinct outcomes. + */ + static branch(successPMF, failurePMF, successProbability) { + let p = successProbability; + if (!Number.isFinite(p)) p = 0; + if (p < 0) p = 0; + if (p > 1) p = 1; + const q = 1 - p; + if (p === 0) return failurePMF.scaleMass(1); + if (p === 1) return successPMF.scaleMass(1); + const eps = successPMF.epsilon ?? failurePMF.epsilon; + const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; + const resultMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of failurePMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); + } + for (const [damageValue, bin] of successPMF.map) { + _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); + } + return new _PMF(resultMap, eps, false, id); + } + /** + * withProbability() + * + * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). + * + * Think of this as a shortcut for: + * pmf.gate(p, PMF.zero()) + * + * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, + * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. + * + * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. + * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. + * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) + * use query.firstSuccessSplit() to get the exact probabilities. + * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). + * + */ + static withProbability(successPMF, probability) { + return _PMF.branch(successPMF, _PMF.zero(), probability); + } + /** + * gate() + * + * A conditional wrapper around branch() that applies this PMF with probability `p`, + * and applies a provided fallback PMF otherwise. + * + * This is useful for modeling a binary choice between two outcomes: + * - The "success" outcome (this PMF) happens with probability `p`. + * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. + * + * Examples: + * - 25% chance to include an opportunity attack, otherwise nothing: + * attackPMF.gate(0.25, PMF.zero()) + * + * - 50% chance to deal fireball damage, otherwise cone of cold damage: + * fireballPMF.gate(0.5, coneOfColdPMF) + * + * Relationship to other helpers: + * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. + * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). + * + * @param p Probability of applying this PMF (between 0 and 1). + * @param fallback PMF to apply when this PMF is *not* selected. + * @returns A new PMF representing the weighted mixture of this PMF and the fallback. + */ + gate(p, fallback) { + return _PMF.branch(this, fallback, p); + } + /** + * PMF.exclusive() + * + * Builds a single PMF from a set of mutually exclusive weighted outcomes. + * Exactly one of the provided options will occur. + * + * Each option has: + * - A PMF representing its outcome (e.g., damage dice). + * - A weight representing its probability of being selected. + * + * Notes: + * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for floating point rounding. + */ + static exclusive(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight) || weight < -eps) { + throw new Error(`PMF.exclusive: invalid weight ${weight}.`); + } + } + let totalWeight = items.reduce((s, { weight }) => s + weight, 0); + if (Math.abs(totalWeight) <= eps) totalWeight = 0; + if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; + if (totalWeight > 1 + EPS) { + throw new Error( + `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` + ); + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (weight > eps) out = out.addScaled(pmf, weight); + } + const leftover = Math.max(0, 1 - totalWeight); + if (leftover > eps) { + out = out.addScaled(_PMF.zero(), leftover); + } + return out; + } + /** + * PMF.mix() + * + * Builds a PMF as a linear combination of input PMFs with the given weights. + * Unlike `exclusive`, this does NOT: + * - enforce that weights sum to 1 + * - add leftover probability to δ0 (PMF.zero()) + * + * Use when outcomes are not mutually exclusive, or for interpolation/blending. + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for skipping tiny weights. + */ + static mix(options, eps = EPS) { + const items = options.map( + (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o + ); + for (const { weight } of items) { + if (!Number.isFinite(weight)) { + throw new Error(`PMF.mix: invalid weight ${weight}.`); + } + } + let out = _PMF.empty(eps); + for (const { pmf, weight } of items) { + if (Math.abs(weight) <= eps) continue; + out = out.addScaled(pmf, weight); + } + return out; + } + /** + * Adds damage attribution metadata to this PMF based on existing count metadata. + * For each bin, sets attr[outcome] = damage × count[outcome]. + * + * This enables damage attribution charts to work with builder-generated PMFs. + * The parser generates attr automatically, but builder PMFs only have count. + * + * @returns New PMF with attr field populated in each bin + */ + /** + * Returns true if this PMF already carries damage attribution metadata. + * + * Only the first positive-damage bin is inspected (parser-generated PMFs + * populate `attr` uniformly), so this is O(1) in practice. + */ + hasAttribution() { + for (const [damage, bin] of this.map) { + if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { + return true; + } + if (damage > 0) break; + } + return false; + } + withAttribution() { + if (this.hasAttribution()) return this; + const newMap = /* @__PURE__ */ new Map(); + for (const [damage, bin] of this.map) { + const attr = {}; + for (const outcome in bin.count) { + const probability = bin.count[outcome]; + if (probability > 0) { + attr[outcome] = damage * probability; + } + } + newMap.set(damage, { + p: bin.p, + count: { ...bin.count }, + attr: Object.keys(attr).length > 0 ? attr : void 0 + }); + } + return new _PMF( + newMap, + this.epsilon, + this.normalized, + `${this.identifier}~attr` + ); + } + /** + * General-purpose N-way mixture. + * weights: Array of [weight, PMF]. + * + * Example: PMF.mixN([ + * [pMiss, zero], + * [pHit, hitPMF], + * [pCrit, critPMF], + * ]); + */ + static mixN(weights, eps = EPS) { + const filtered = weights.filter(([w]) => w > eps); + if (filtered.length === 0) { + return _PMF.emptyMass(); + } + let acc = null; + let sum = 0; + for (const [w, pmf] of filtered) { + if (acc === null) { + acc = pmf; + sum = w; + } else { + const q = w / (sum + w); + acc = _PMF.branch(pmf, acc, q); + sum += w; + } + } + return acc ?? _PMF.emptyMass(); + } + // This is a convenience method for when we use power + // TODO: It can be smarter in the future, and we can also add it to query + // That way statistics operations on invalid PMFs can throw an error + // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? + setPreservedProvenance(preserved) { + if (!this._preservedProvenance && preserved) { + throw new Error( + "Preserved provenance is already set to false, cannot fix that" + ); + } + this._preservedProvenance = preserved; + } + preservedProvenance() { + return this._preservedProvenance; + } + getPowerCacheKey(n, eps) { + const id = this.identifier; + let key = `${id}`; + for (let i = 1; i < n; i++) key += `+${id}`; + return `${key}@${eps}`; + } + /** + * Efficiently computes this PMF convolved with itself `n` times. + * Uses exponentiation by squaring to reduce total convolutions. + * n must be a positive integer. + * * + * * NOTE: This folds multiple independent attacks into a single PMF. + * As a result, The power() method causes a loss of data provenance. + * This is ONLY SAFE if you are trying to calculate masses. + * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). + */ + power(n, eps = this.epsilon) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("power(n): n must be a positive integer"); + } + if (n === 1) return this; + const epsilon = eps ?? this.epsilon; + const key = this.getPowerCacheKey(n, epsilon); + { + const cached = pmfCache?.get(key); + if (cached) return cached; + } + let base = this.normalized ? this : this.normalize(); + let result = base; + let exp = n - 1; + while (exp > 0) { + if (exp & 1) { + result = result.convolve(base, epsilon); + } + exp >>= 1; + if (exp > 0) { + base = base.convolve(base, epsilon); + } + } + result.setPreservedProvenance(false); + { + pmfCache?.set(key, result); + } + return result; + } + /* + * Helper for chaining multiple identical attacks + */ + replicate(n) { + if (!Number.isInteger(n) || n <= 0) { + throw new Error("replicate(n): n must be a positive integer"); + } + if (n === 1) return [this]; + return Array.from({ length: n }, () => this); + } + mass() { + if (this._totalMass === void 0) { + let totalProbabilityMass = 0; + for (const { p } of this.map.values()) { + totalProbabilityMass += p; + } + this._totalMass = totalProbabilityMass; + } + return this._totalMass; + } + outcomeMass(outcome) { + let totalProbabilityMass = 0; + for (const { p, count } of this.map.values()) { + totalProbabilityMass += p * (count[outcome] ?? 0); + } + return totalProbabilityMass; + } + // Helper for testing + faceTotal() { + return [...this.map.keys()].reduce((sum, key) => sum + key, 0); + } + normalize() { + if (this.normalized) return this; + const normalizationFactor = this.mass(); + if (normalizationFactor === 0) return this; + const normalizedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const normalizedCount = {}; + for (const labelKey in probabilityBin.count) { + normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; + } + let normalizedAttributes; + if (probabilityBin.attr) { + normalizedAttributes = {}; + for (const labelKey in probabilityBin.attr) { + normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; + } + } + normalizedMap.set(damageValue, { + p: probabilityBin.p / normalizationFactor, + count: normalizedCount, + attr: normalizedAttributes + }); + } + return new _PMF(normalizedMap, this.epsilon, true, this.identifier); + } + /** + * Returns a copy with negligible probabilities removed (p < eps). + * If keepFinalBin is true, the bin with the largest key is always kept, + * even if its probability is below eps. count/attr submaps are still cleaned. + */ + compact(eps = this.epsilon, keepFinalBin = false) { + let maxKey = -Infinity; + if (keepFinalBin) { + for (const key of this.map.keys()) { + if (key > maxKey) maxKey = key; + } + } + const compactedMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; + if (!shouldKeep) continue; + const cleanedBin = _PMF.cloneBin(probabilityBin); + for (const labelKey in cleanedBin.count) { + if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { + delete cleanedBin.count[labelKey]; + } + } + if (cleanedBin.attr) { + for (const labelKey in cleanedBin.attr) { + if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { + delete cleanedBin.attr[labelKey]; + } + } + if (Object.keys(cleanedBin.attr).length === 0) { + cleanedBin.attr = void 0; + } + } + compactedMap.set(damageValue, cleanedBin); + } + return new _PMF(compactedMap, eps, this.normalized, this.identifier); + } + // Note: The "support" of a PMF is the set of all non-zero probability outcomes. + // This returns all damage values with non-zero probability, sorted ascending. + support() { + if (this._support === void 0) { + this._support = [...this.map.keys()].sort((a, b) => a - b); + } + return this._support; + } + // Minimum possible damage value. + min() { + if (this._min === void 0) { + const support = this.support(); + this._min = support.length > 0 ? support[0] : 0; + } + return this._min; + } + // Maximum possible damage value. + max() { + if (this._max === void 0) { + const support = this.support(); + this._max = support.length > 0 ? support[support.length - 1] : 0; + } + return this._max; + } + /** + * Returns the expected (mean) damage value. + * Cached for performance since this requires iterating through all bins. + */ + mean() { + if (this._mean === void 0) { + let totalSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + totalSum += damageValue * probabilityBin.p; + } + this._mean = totalSum; + } + return this._mean; + } + /** + * Returns the variance of the damage distribution. + * Cached for performance since this requires mean calculation plus iteration. + */ + variance() { + if (this._variance === void 0) { + const meanValue = this.mean(); + let varianceSum = 0; + for (const [damageValue, probabilityBin] of this.map) { + const deviationFromMean = damageValue - meanValue; + varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; + } + this._variance = varianceSum; + } + return this._variance; + } + /** + * Returns the standard deviation of the damage distribution. + */ + stdev() { + if (this._stdev === void 0) { + this._stdev = Math.sqrt(this.variance()); + } + return this._stdev; + } + /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ + static cloneBin(bin) { + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ + static scaleBin(bin, factor) { + const count = {}; + for (const k in bin.count) { + count[k] = bin.count[k] * factor; + } + let attr; + if (bin.attr) { + attr = {}; + for (const k in bin.attr) { + attr[k] = bin.attr[k] * factor; + } + } + return { p: bin.p * factor, count, attr }; + } + static mergeInto(destinationMap, damageValue, binToAdd) { + const existingBin = destinationMap.get(damageValue); + if (!existingBin) { + destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); + return; + } + existingBin.p += binToAdd.p; + for (const labelKey in binToAdd.count) { + existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; + } + if (binToAdd.attr) { + if (!existingBin.attr) { + existingBin.attr = {}; + } + for (const labelKey in binToAdd.attr) { + existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; + } + } + } + // Convenience method + add(other) { + return this.addScaled(other, 1); + } + /** + * Returns a new PMF with a scaled branch added to this one. + * The branch PMF is scaled by the given probability before merging + * This will be very useful for conditional effects and for being + * able to model "I can probably have this opportunity attack 40% of rounds" + * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes + */ + addScaled(branch, probability) { + if (probability === 0) return this; + const resultMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of this.map) { + resultMap.set(dmg, _PMF.cloneBin(bin)); + } + for (const [damageValue, probabilityBin] of branch.map) { + _PMF.mergeInto( + resultMap, + damageValue, + _PMF.scaleBin(probabilityBin, probability) + ); + } + return new _PMF( + resultMap, + this.epsilon, + false, + `${this.identifier}+scaled(${branch.identifier},${probability})` + ); + } + /** + * Redistributes probability mass to model an effect that only occurs with + * probability `frequency` — a conditional attack, an on-hit rider, or a + * sub-one AoE target fraction. + * + * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, + * per-label `count`, AND per-label `attr` — and the freed mass is moved into + * the miss bin at damage 0, tagged with the canonical `missNone` outcome. + * Total probability mass is preserved. + * + * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage + * attribution (`attr`) intact, so a frequency-scaled PMF still renders + * correctly in the damage-attribution charts. + * + * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` + * collapses all mass into the miss bin. The miss outcome is assumed to be + * encoded at damage value 0. + * + * @param frequency Probability in [0, 1] that the effect occurs. + */ + applyHitFrequency(frequency) { + if (!Number.isFinite(frequency) || frequency >= 1) return this; + const freq = Math.max(0, frequency); + const pMiss = this.pAt(0); + const pHit = 1 - pMiss; + const newMissMass = pMiss + (1 - freq) * pHit; + const newMap = /* @__PURE__ */ new Map(); + newMap.set(0, { + p: newMissMass, + count: { [MISS_NONE_OUTCOME]: newMissMass }, + attr: {} + }); + for (const [damage, bin] of this.map) { + if (damage <= 0) continue; + newMap.set(damage, _PMF.scaleBin(bin, freq)); + } + return new _PMF( + newMap, + this.epsilon, + false, + `freq(${this.identifier},${freq})` + ); + } + scaleMass(factor) { + if (factor === 1) return this; + const scaledMap = /* @__PURE__ */ new Map(); + for (const [damageValue, probabilityBin] of this.map) { + scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); + } + return new _PMF( + scaledMap, + this.epsilon, + false, + `scale(${this.identifier},${factor})` + ); + } + mapDamage(damageTransformFunction) { + const transformedMap = /* @__PURE__ */ new Map(); + for (const [originalDamage, probabilityBin] of this.map) { + const transformedDamage = damageTransformFunction(originalDamage); + _PMF.mergeInto( + transformedMap, + transformedDamage, + _PMF.cloneBin(probabilityBin) + ); + } + return new _PMF( + transformedMap, + this.epsilon, + this.normalized, + `map(${this.identifier})` + ); + } + scaleDamage(factor, rounding = "floor") { + const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; + return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); + } + getPMFCombineCacheKey(p1, p2, eps, raw) { + const [id1, id2] = [p1.identifier, p2.identifier].sort(); + return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; + } + /** + * A small content fingerprint (mass + bin count + face sum) so convolution + * cache keys change if the underlying numbers do. Memoized because a PMF is + * immutable once constructed — this avoids re-summing every key on each + * convolve() call (including cache hits). + */ + fingerprint() { + if (this._fingerprint === void 0) { + let faceSum = 0; + for (const k of this.map.keys()) faceSum += k; + this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; + } + return this._fingerprint; + } + convolve(other, eps, raw = false) { + const epsilon = eps ?? this.epsilon; + const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); + const A0 = norm(this); + const B0 = norm(other); + const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; + const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); + const cached = pmfCache?.get(cacheKey); + if (cached) return cached; + const combinedMap = /* @__PURE__ */ new Map(); + for (const [aVal, aBin] of A.map) { + const ap = aBin.p; + const aCount = aBin.count; + const aAttr = aBin.attr; + for (const [bVal, bBin] of B.map) { + const bp = bBin.p; + const dmg = aVal + bVal; + let dest = combinedMap.get(dmg); + if (dest === void 0) { + dest = { p: 0, count: {} }; + combinedMap.set(dmg, dest); + } + dest.p += ap * bp; + const dc = dest.count; + for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; + for (const k in bBin.count) + dc[k] = (dc[k] || 0) + bBin.count[k] * ap; + if (aAttr || bBin.attr) { + let da = dest.attr; + if (da === void 0) { + da = {}; + dest.attr = da; + } + if (aAttr) + for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; + if (bBin.attr) + for (const k in bBin.attr) + da[k] = (da[k] || 0) + bBin.attr[k] * ap; + } + } + } + let result = new _PMF( + combinedMap, + epsilon, + !raw, + `${A.identifier}${raw ? "*" : "+"}${B.identifier}` + ); + const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); + const mGot = result.mass(); + if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { + result = result.scaleMass(mExp / mGot); + } + if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) + result = result.normalize(); + pmfCache?.set(cacheKey, result); + return result; + } + // 3) Nice wrapper so you can call pmf.combineRaw(other) + combineRaw(other, eps) { + return this.convolve(other, eps, true); + } + // Reduce a list of PMFs by left-folding convolve() with the given eps + static reduceConvolveLeft(pmfList, eps) { + let result = pmfList[0]; + for (let i = 1; i < pmfList.length; i++) { + result = result.convolve(pmfList[i], eps); + } + return result; + } + /** + * Convolves multiple PMFs using linear convolution with automatic caching. + * Uses a left-to-right accumulation approach for maximum cache reuse. + * Each convolve() call automatically uses the convolution cache for performance. + * + * This linear approach provides better cache hits than pairwise because: + * - Intermediate results are more predictable and stable + * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) + * - Order-independent cache keys work better with consistent build patterns + */ + static convolveMany(pmfList, eps = EPS) { + if (pmfList.length === 0) return _PMF.empty(eps); + if (pmfList.length === 1) return pmfList[0]; + return _PMF.reduceConvolveLeft(pmfList, eps); + } + /** + * Returns a plain, JSON-serializable representation of this PMF. + * + * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces + * the expected output (no double-encoding). Use {@link PMF.fromJSON} to + * reconstruct, or {@link PMF.toJSONString} if you need the string directly. + */ + toJSON() { + return { + bins: [...this.map.entries()], + normalized: this.normalized, + identifier: this.identifier + }; + } + /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ + toJSONString() { + return JSON.stringify(this); + } + static fromJSON(jsonData) { + return new _PMF( + new Map(jsonData.bins), + EPS, + !!jsonData.normalized, + jsonData.identifier || "fromJSON" + ); + } + /** + * Relative pruning with optional top-K floor. + * Keeps bins with p >= epsRel * peak, always keeps min and max damage, + * optionally guarantees at least `minBins` survivors by adding top-K. + * Returns a new, non-normalized PMF. + */ + prune(epsRel, minBins = 0) { + const size = this.map.size; + if (size === 0) return this; + let peak = 0; + let minDamage = Number.POSITIVE_INFINITY; + let maxDamage = Number.NEGATIVE_INFINITY; + for (const [dmg, bin] of this.map) { + if (bin.p > peak) peak = bin.p; + if (dmg < minDamage) minDamage = dmg; + if (dmg > maxDamage) maxDamage = dmg; + } + if (peak === 0) + return new _PMF(new Map(this.map), epsRel, false, this.identifier); + const thresh = epsRel * peak; + const entries = [...this.map.entries()]; + const survivorsByDmg = /* @__PURE__ */ new Map(); + const protect = (d) => { + const b = this.map.get(d); + if (b) survivorsByDmg.set(d, b); + }; + protect(minDamage); + if (maxDamage !== minDamage) protect(maxDamage); + for (const [dmg, bin] of entries) { + if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); + } + if (minBins > 0 && survivorsByDmg.size < minBins) { + entries.sort((a, b) => b[1].p - a[1].p); + for (const [dmg, bin] of entries) { + if (!survivorsByDmg.has(dmg)) { + survivorsByDmg.set(dmg, bin); + if (survivorsByDmg.size >= minBins) break; + } + } + } + const prunedMap = /* @__PURE__ */ new Map(); + for (const [dmg, bin] of survivorsByDmg) { + const newCount = {}; + for (const k in bin.count) { + const v = bin.count[k]; + if (Math.abs(v) >= thresh) newCount[k] = v; + } + let newAttr; + if (bin.attr) { + for (const k in bin.attr) { + const v = bin.attr[k]; + if (Math.abs(v) >= thresh) { + if (!newAttr) newAttr = {}; + newAttr[k] = v; + } + } + } + prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); + } + return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); + } + /** Probability mass at exactly x. */ + pAt(x) { + return this.map.get(x)?.p ?? 0; + } + /** + * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. + * Assumes a miss is encoded as the damage-0 bin (the convention used across + * attack/save PMFs). The dual of {@link missProbability}. + */ + hitProbability() { + return 1 - this.pAt(0); + } + /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ + missProbability() { + return this.pAt(0); + } + /** + * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width + * damage buckets, aggregating probability mass (and `count`/`attr` + * provenance) into each bucket's start value. Returns this PMF unchanged when + * its integer support already fits within `maxBuckets`. + * + * This is a lossy display/downsampling transform (bucket start replaces the + * exact damage value) — use it for charting wide distributions, not for DPR + * math. + */ + rebin(maxBuckets) { + if (!(maxBuckets > 0)) return this; + const support = this.support(); + if (support.length === 0) return this; + const min = support[0]; + const max = support[support.length - 1]; + const range = max - min; + if (range + 1 <= maxBuckets) return this; + const binSize = Math.ceil((range + 1) / maxBuckets); + return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize); + } + /** Dense integer support from min..max (inclusive). + * Useful for showing empty bars in charts. + */ + denseSupport() { + const s = this.support(); + if (s.length === 0) return []; + const lo = Math.min(...s), hi = Math.max(...s); + return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( + (a, b) => a - b + ); + } + /** CDF at x: P(X ≤ x). */ + cdfAt(x) { + let acc = 0; + for (const [val, bin] of this.map) if (val <= x) acc += bin.p; + return acc; + } + /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ + quantile(p) { + if (this.map.size === 0) return 0; + const s = this.support().sort((a, b) => a - b); + let acc = 0; + for (const x of s) { + acc += this.pAt(x); + if (acc >= p) return x; + } + return s[s.length - 1]; + } + /** Get outcome probability at specific damage value. */ + outcomeAt(damage, outcome) { + return this.map.get(damage)?.count[outcome] ?? 0; + } + /** Get all outcome types present in this PMF. */ + outcomes() { + const outcomeSet = /* @__PURE__ */ new Set(); + for (const [, bin] of this.map) { + for (const outcome in bin.count) { + if (bin.count[outcome] > 0) { + outcomeSet.add(outcome); + } + } + } + return Array.from(outcomeSet).sort(); + } + /** Get total probability of an outcome across all damage values. */ + outcomeProbability(outcome) { + let total = 0; + for (const [, bin] of this.map) { + total += bin.count[outcome] ?? 0; + } + return total; + } + /** Get damage attribution for an outcome at specific damage value. */ + outcomeAttributionAt(damage, outcome) { + return this.map.get(damage)?.attr?.[outcome] ?? 0; + } + /** Get all outcome data at specific damage value. */ + binAt(damage) { + const bin = this.map.get(damage); + if (!bin) return null; + return { + p: bin.p, + count: { ...bin.count }, + attr: bin.attr ? { ...bin.attr } : void 0 + }; + } + /** Check if outcome exists in this PMF. */ + hasOutcome(outcome) { + for (const [, bin] of this.map) { + if ((bin.count[outcome] ?? 0) > 0) { + return true; + } + } + return false; + } + /** + * Split each damage value's probability mass across outcome labels, returning + * per-label maps of `damage value → probability mass attributable to that + * label`. Summing over labels at a given value recovers that value's `p`. + * + * Damage-bearing bins are split by `attr` weight (the share of damage each + * outcome contributed); the clean-miss bin at 0 is split by `count` weight + * (there is no damage to attribute). Attribution is computed on demand via + * {@link withAttribution} when absent, so builder-generated PMFs work too. + * + * This is the provenance core of the stacked damage-attribution chart — the + * caller only maps these series into its rendering format (colors, binning, + * axis labels). + */ + attributionByValue() { + const src = this.hasAttribution() ? this : this.withAttribution(); + const result = /* @__PURE__ */ new Map(); + const add = (label, damage, mass) => { + if (!(mass > 0)) return; + let series = result.get(label); + if (!series) { + series = /* @__PURE__ */ new Map(); + result.set(label, series); + } + series.set(damage, (series.get(damage) ?? 0) + mass); + }; + for (const [damage, bin] of src.map) { + const p = bin.p || 0; + if (p <= 0) continue; + const isMissBin = damage === 0; + if (isMissBin) { + let totalCount = 0; + for (const k in bin.count) totalCount += bin.count[k] || 0; + if (totalCount > 0) { + const c = bin.count[MISS_NONE_OUTCOME] || 0; + add(MISS_NONE_OUTCOME, damage, c / totalCount * p); + } + continue; + } + let totalAttr = 0; + if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; + if (bin.attr && totalAttr > 0) { + for (const k in bin.attr) { + if (k === MISS_NONE_OUTCOME) continue; + add(k, damage, (bin.attr[k] || 0) / totalAttr * p); + } + } + } + return result; + } + tailProbGE(t) { + let s = 0; + for (const [x, bin] of this) { + if (bin.p > 0 && x >= t) s += bin.p; + } + return s; + } + tailProbGT(t) { + let s = 0; + for (const [x, rec] of this) { + if (x > t) s += rec.p; + } + return s; + } + /** + * Returns a new PMF containing only bins where the specified outcome has non-zero probability. + * This creates a marginal distribution for the given outcome type, with probabilities + * scaled to represent the unconditional mass attributable to that outcome. + */ + filterOutcome(outcome) { + const filteredMap = /* @__PURE__ */ new Map(); + for (const [damageValue, bin] of this.map) { + const outcomeCount = bin.count[outcome] ?? 0; + const totalCount = Object.values(bin.count ?? {}).reduce( + (a, b) => (a ?? 0) + (b ?? 0), + 0 + ); + if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { + const proportion = outcomeCount / totalCount; + const newP = bin.p * proportion; + const newCount = { [outcome]: outcomeCount }; + let newAttr; + if (bin.attr && bin.attr[outcome] !== void 0) { + newAttr = { [outcome]: bin.attr[outcome] * proportion }; + } + filteredMap.set(damageValue, { + p: newP, + count: newCount, + attr: newAttr + }); + } + } + return new _PMF( + filteredMap, + this.epsilon, + false, + // don't normalize by default + `filter(${this.identifier},${outcome})` + ); + } + /** + * Calculates probabilities for first-success outcomes across n independent attempts. + * + * @param pSuccess - Total probability of any success on a single attempt. + * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). + * @param n - Number of independent attempts. + * + * Returns: + * - pSpecificSuccess: Probability that the first success was of the "special" type + * - pGeneralSuccess: Probability that the first success was of the non-special type + * - pNone: Probability that no successes occurred + * - pAny: Probability that at least one success occurred + */ + static firstSuccessWeights(pSuccess, pSpecial, n) { + if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { + throw new Error( + `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` + ); + } + const pFail = 1 - pSuccess; + const pFailAll = Math.pow(pFail, n); + const pAny = 1 - pFailAll; + const denom = pSuccess === 0 ? 1 : pSuccess; + const pSpecificSuccess = pSpecial * pAny / denom; + const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; + const pNone = 1 - pSpecificSuccess - pGeneralSuccess; + return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; + } + mapValues(f, eps = EPS, opts) { + const rounding = opts?.rounding ?? "none"; + const preserveCounts = opts?.preserveCounts ?? true; + const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; + const probs = /* @__PURE__ */ new Map(); + const counts = /* @__PURE__ */ new Map(); + for (const [v, bin] of this) { + if (Math.abs(bin.p) < eps) continue; + const u = round(f(v)); + probs.set(u, (probs.get(u) ?? 0) + bin.p); + if (preserveCounts) { + const src = bin.count; + if (src) { + const dest = counts.get(u) ?? {}; + for (const k in src) { + dest[k] = (dest[k] ?? 0) + src[k]; + } + counts.set(u, dest); + } + } + } + const internal = /* @__PURE__ */ new Map(); + for (const [u, p] of probs) { + internal.set(u, { p, count: counts.get(u) ?? {} }); + } + return _PMF.fromMap( + new Map(Array.from(internal, ([u, b]) => [u, b.p])), + eps + ); + } + static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { + const filtered = []; + for (const [v, p] of m) { + if (!Number.isFinite(v) || !Number.isFinite(p)) continue; + if (p <= 0 || Math.abs(p) < eps) continue; + if (requireIntegerValues && !Number.isInteger(v)) { + throw new Error(`fromMap: non-integer outcome ${v}`); + } + filtered.push([v, p]); + } + if (filtered.length === 0) { + throw new Error("fromMap: empty or invalid input map"); + } + let sum = 0; + let c = 0; + for (const [, p] of filtered) { + const y = p - c; + const t = sum + y; + c = t - sum - y; + sum = t; + } + if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); + filtered.sort((a, b) => a[0] - b[0]); + const internal = /* @__PURE__ */ new Map(); + for (const [v, p] of filtered) { + internal.set(v, { p: p / sum, count: {} }); + } + return new _PMF(internal, eps); + } + query() { + return new DiceQuery(this); + } +}; +// Unique ID generator for anonymous PMFs to avoid cache key collisions +_PMF.__anonIdCounter = 1; +var PMF = _PMF; + +// src/parser/dice.ts +var MAX_BINARY_OUTCOMES = 1e8; +var Dice = class _Dice { + constructor(x = 0) { + this.faces = {}; + this.privateData = {}; + // Partial: the object starts empty and gains keys as outcomes are recorded, + // so the type must not claim every OutcomeType is present. (Previously typed + // as a full Record via an `as` cast, which lied about missing keys.) + this.outcomeData = {}; + this.hasHitDistributionCalculated = false; + if (x <= 0) return; + for (let i = 1; i <= x; i++) { + this.faces[i] = 1; + } + } + getOutcomeDistribution(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const distribution = this.outcomeData[key]; + if (distribution === void 0) return void 0; + return { ...distribution }; + } + getFullOutcomeDistribution() { + return { ...this.outcomeData }; + } + setOutcomeDistribution(key, data) { + if (data) { + this.outcomeData[key] = data; + } else { + delete this.outcomeData[key]; + } + } + hasOutcomeData(key) { + if (key === "hit") { + this.ensureHitDistribution(); + } + const data = this.outcomeData[key]; + return data !== void 0 && Object.keys(data).length > 0; + } + getOutcomeCount(key, face) { + return this.outcomeData[key]?.[face] ?? 0; + } + getAverage(key) { + const distribution = this.getOutcomeDistribution(key); + if (!distribution) return 0; + const totalCount = Object.values(distribution).reduce( + (sum, count) => sum + count, + 0 + ); + const expectedDamage = Object.entries(distribution).reduce( + (sum, [damage, count]) => sum + Number(damage) * count, + 0 + ); + if (totalCount === 0) return 0; + return expectedDamage / totalCount; + } + // TODO this can be private later if we change how testing works + calculateHitDistribution() { + const hitValues = {}; + const subtractedOutcomes = [ + this.outcomeData.crit, + this.outcomeData.missNone, + this.outcomeData.missDamage, + this.outcomeData.saveHalf, + this.outcomeData.saveFail, + this.outcomeData.pc + ]; + for (const [face, totalCount] of Object.entries(this.faces)) { + const numFace = Number(face); + let hitCount = totalCount; + for (const distribution of subtractedOutcomes) { + const outcomeCount = distribution?.[numFace]; + if (outcomeCount) { + hitCount -= outcomeCount; + } + } + if (numFace === 0) { + hitCount = 0; + } + if (hitCount < 0) { + hitCount = 0; + } + hitValues[numFace] = hitCount; + } + return hitValues; + } + ensureHitDistribution() { + if (!this.hasHitDistributionCalculated) { + const hitValues = this.calculateHitDistribution(); + this.setOutcomeDistribution("hit", hitValues); + this.hasHitDistributionCalculated = true; + } + } + // PRIVATE FUNCTIONS + binaryOp(other, op, diceConstructor) { + const result = diceConstructor ? diceConstructor() : new _Dice(); + const isScalar = typeof other === "number"; + const keys1 = this.keys(); + const keys2 = isScalar ? [] : other.keys(); + if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { + throw new DiceParseError( + `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` + ); + } + for (const key1 of keys1) { + const value1 = this.faces[key1]; + if (isScalar) { + const resultKey = op(key1, other); + result.increment(resultKey, value1); + } else { + for (const key2 of keys2) { + const value2 = other.faces[key2]; + const resultKey = op(key1, key2); + result.increment(resultKey, value1 * value2); + } + } + } + return result; + } + removeFaces(facesToRemove) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (!facesToRemove.includes(numKey)) { + result.faces[numKey] = value; + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // PUBLIC FUNCTIONS + getFaceEntries() { + return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); + } + getFaceMap() { + return { ...this.faces }; + } + get(face) { + return this.faces[face] ?? 0; + } + keys() { + return Object.keys(this.faces).map(Number); + } + values() { + return Object.values(this.faces); + } + total() { + return Object.values(this.faces).reduce((sum, value) => sum + value, 0); + } + setFace(key, value) { + this.faces[key] = value; + } + static scalar(value) { + const result = new _Dice(); + result.increment(value, 1); + return result; + } + maxFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.max(...numericKeys); + } + minFace() { + const numericKeys = this.keys(); + if (numericKeys.length === 0) { + throw new Error("No numeric faces found"); + } + return Math.min(...numericKeys); + } + increment(face, count) { + const current = this.faces[face] || 0; + this.faces[face] = current + count; + } + normalize(scalar) { + const result = new _Dice(); + for (const [face, count] of Object.entries(this.faces)) { + result.faces[Number(face)] = count * scalar; + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + // OPERATIONS + add(other) { + return this.binaryOp(other, (a, b) => a + b); + } + subtract(other) { + return this.binaryOp(other, (a, b) => a - b); + } + conditionalApply(other) { + return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); + } + multiply(other) { + return this.binaryOp(other, (a, b) => a * b); + } + addNonZero(other) { + return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); + } + eq(other) { + return this.binaryOp(other, (a, b) => a === b ? 1 : 0); + } + max(other) { + return this.binaryOp(other, (a, b) => Math.max(a, b)); + } + min(other) { + return this.binaryOp(other, (a, b) => Math.min(a, b)); + } + advantage() { + return this.max(this); + } + ge(other) { + return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); + } + divide(other) { + return this.binaryOp(other, (a, b) => a / b); + } + divideRoundUp(other) { + return this.binaryOp(other, (a, b) => Math.ceil(a / b)); + } + divideRoundDown(other) { + return this.binaryOp(other, (a, b) => Math.floor(a / b)); + } + and(other) { + return this.binaryOp(other, (a, b) => a && b ? 1 : 0); + } + checkTarget(other, comparisonLogic) { + const createResult = () => { + const result = new _Dice(); + result.increment(0, 0); + result.increment(1, 0); + return result; + }; + return this.binaryOp(other, comparisonLogic, createResult); + } + dc(other) { + const dcCheck = (a, b) => a >= b ? 0 : 1; + const result = this.checkTarget(other, dcCheck); + result.privateData.isDCCheck = true; + return result; + } + ac(other) { + const acCheck = (a, b) => a >= b ? a : 0; + return this.checkTarget(other, acCheck); + } + deleteFace(face) { + const result = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + if (numKey !== face) { + result.increment(numKey, value); + } + } + result.privateData = { ...this.privateData }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + reroll(toReroll) { + const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; + const rerollKeys = rerollDice.keys(); + const rerollSet = new Set(rerollKeys); + const removed = this.removeFaces(rerollKeys); + let result = new _Dice(); + for (const face of this.keys()) { + const wasRerolled = rerollSet.has(face); + result = result.combine(removed); + if (wasRerolled) { + result = result.combine(this); + } + } + return result; + } + // This is not addition and not rolling two dice at once. + // Instead, it’s mixing two distributions into a single weighted die. + combine(other) { + if (typeof other === "number") { + other = _Dice.scalar(other); + } + const result = new _Dice(); + for (const [key, value] of Object.entries(other.faces)) { + result.faces[Number(key)] = value; + } + const except = new _Dice(); + for (const [key, value] of Object.entries(this.faces)) { + const numKey = Number(key); + result.increment(numKey, value); + if (!(numKey in other.faces)) { + except.increment(numKey, value); + } + } + result.privateData = { ...this.privateData, except: other }; + result.outcomeData = { ...this.outcomeData }; + return result; + } + combineInPlace(other) { + for (const [key, value] of Object.entries(other.faces)) { + const numKey = Number(key); + const current = this.faces[numKey] || 0; + this.faces[numKey] = current + value; + } + } + percent() { + const total = this.total(); + const result = {}; + for (const [face, count] of Object.entries(this.faces)) { + result[Number(face)] = count / total; + } + return result; + } + average() { + const total = this.total(); + if (total === 0) return 0; + let sum = 0; + for (const [key, value] of Object.entries(this.faces)) { + sum += Number(key) * value; + } + return sum / total; + } + /* + * Convert dice to PMF using OutcomeType labels directly from damage distribution. + * This is much cleaner than the original complex distribution conversion. + */ + toPMF(numEpsilon = EPS) { + const total = this.total(); + if (total === 0) return PMF.empty(numEpsilon); + this.ensureHitDistribution(); + const map = /* @__PURE__ */ new Map(); + const hitDistro = this.getOutcomeDistribution("hit") || {}; + const critDistro = this.getOutcomeDistribution("crit") || {}; + const missDistro = this.getOutcomeDistribution("missDamage") || {}; + const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; + const pcDistro = this.getOutcomeDistribution("pc") || {}; + const isSaveHalf = Object.keys(saveDistro).length > 0; + const isDCCheck = this.privateData.isDCCheck === true; + const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; + for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { + const face = Number(faceStr); + const faceCount = Number(faceCountRaw); + if (faceCount <= 0) continue; + let p = faceCount / total; + p = clampNonNeg(p); + if (!(p > 0)) continue; + if (numEpsilon >= 0 && p < numEpsilon) continue; + const count = {}; + const attr = {}; + if (hitDistro[face]) { + const c = clampNonNeg(hitDistro[face] / total); + if (c > 0) { + if (isSaveHalf || isDCCheck) { + count.saveFail = c; + attr.saveFail = clampNonNeg(face * hitDistro[face] / total); + } else { + count.hit = c; + attr.hit = clampNonNeg(face * hitDistro[face] / total); + } + } + } + if (critDistro[face]) { + const c = clampNonNeg(critDistro[face] / total); + if (c > 0) { + count.crit = c; + attr.crit = clampNonNeg(face * critDistro[face] / total); + } + } + if (missDistro[face]) { + const c = clampNonNeg(missDistro[face] / total); + if (c > 0) { + count.missDamage = c; + attr.missDamage = clampNonNeg(face * missDistro[face] / total); + } + } + if (saveDistro[face]) { + const c = clampNonNeg(saveDistro[face] / total); + if (c > 0) { + if (isSaveHalf) { + count.saveHalf = c; + attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); + } else { + count.saveFail = (count.saveFail ?? 0) + c; + attr.saveFail = clampNonNeg( + (attr.saveFail ?? 0) + face * saveDistro[face] / total + ); + } + } + } + if (pcDistro[face]) { + const c = clampNonNeg(pcDistro[face] / total); + if (c > 0) { + count.pc = c; + attr.pc = clampNonNeg(face * pcDistro[face] / total); + } + } + if (!isSaveHalf && !isDCCheck) { + const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); + const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); + if (unaccountedCount > 0) { + const frac = clampNonNeg(unaccountedCount / total); + if (frac > 0) { + count.missNone = (count.missNone ?? 0) + frac; + } + } + } + const bin = { p, count }; + if (Object.keys(attr).length > 0) { + bin.attr = attr; + } + map.set(face, bin); + } + const identifier = this.identifier || "ERROR"; + return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); + } +}; + +// src/parser/parser.ts +var MAX_DIE_SIDES = 1e6; +var MAX_DICE_COUNT = 1e4; +var MAX_KEEP_OUTCOMES = 1e6; +var parseCache = new LRUCache(1e3); +var cachingEnabled = true; +function setCachingEnabled(enabled) { + cachingEnabled = enabled; + if (!enabled) clearParserCache(); +} +function getCachingEnabled() { + return cachingEnabled; +} +function clearParserCache() { + parseCache.clear(); +} +function parse(expression, n = 0) { + const cleaned = expression.replace(/ /g, "").toLowerCase(); + if (cachingEnabled) { + const cacheKey = `${cleaned}:${n}`; + const cached = parseCache.get(cacheKey); + if (cached) return cached; + } + const chars = [...cleaned]; + let result; + try { + result = parseExpression(chars, n); + } catch (error) { + throw new DiceParseError( + `Cannot parse dice expression [${expression}]: ${error}`, + { expression, cause: error } + ); + } + result.privateData = result.privateData || {}; + result.identifier = cleaned; + if (chars.length > 0) { + throw new DiceParseError( + `Unexpected token: '${chars[0]}' from expression: '${expression}'`, + { expression } + ); + } + const resultPMF = result.toPMF(-1); + if (cachingEnabled) { + const cacheKey = `${cleaned}:${n}`; + parseCache.set(cacheKey, resultPMF); + } + return resultPMF; +} +function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { + dice = dice.normalize(currentNorm); + finalResult = finalResult.normalize(normValue); + finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); + finalResult = finalResult.combine(dice); + return { newNorm: currentNorm * normValue, updatedResult: finalResult }; +} +function parseExpression(arr, n) { + const result = (() => { + const res = parseArgument(arr, n); + return typeof res === "number" ? Dice.scalar(res) : res; + })(); + let op = parseOperation(arr); + let finalResult = result; + while (op != null) { + const arg = !op.unary ? parseArgument(arr, n) : finalResult; + let crit; + let critNorm = 1; + if (arr[0] === "x" || arr[0] === "c") { + const isXcrit = arr[0] === "x"; + if (isXcrit) assertToken(arr, "x"); + assertToken(arr, "c"); + assertToken(arr, "r"); + assertToken(arr, "i"); + assertToken(arr, "t"); + const count = isXcrit ? parseNumber(arr, n) : 1; + crit = new Dice(); + for (let i = 0; i < count; i++) { + const max = finalResult.maxFace(); + crit.setFace(max, finalResult.get(max)); + finalResult = finalResult.deleteFace(max); + } + critNorm = crit.total(); + crit = op.call(crit, parseBinaryArgument(arg, arr, n)); + critNorm = crit && critNorm ? crit.total() / critNorm : 1; + } + let save; + let saveNorm = 1; + if (arr[0] === "s") { + assertToken(arr, "s"); + assertToken(arr, "a"); + assertToken(arr, "v"); + assertToken(arr, "e"); + save = new Dice(); + const min = finalResult.minFace(); + save.increment(min > 0 ? min : 1, finalResult.get(min)); + saveNorm = save.total(); + finalResult = finalResult.deleteFace(min); + save = op.call(save, parseBinaryArgument(arg, arr, n)); + saveNorm = save && saveNorm ? save.total() / saveNorm : 1; + } + let pc; + let pcNorm = 1; + if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { + assertToken(arr, "p"); + assertToken(arr, "c"); + pc = new Dice(); + const min = finalResult.minFace(); + pc.increment(min > 0 ? min : 1, finalResult.get(min)); + const missBefore = pc.total(); + finalResult = finalResult.deleteFace(min); + pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); + const missAfter = pc ? pc.total() : 0; + pcNorm = missBefore ? missAfter / missBefore : 1; + } + let miss; + let missNorm = 1; + if (arr[0] === "m") { + assertToken(arr, "m"); + assertToken(arr, "i"); + assertToken(arr, "s"); + assertToken(arr, "s"); + miss = new Dice(); + const min = finalResult.minFace(); + miss.increment(min > 0 ? min : 1, finalResult.get(min)); + missNorm = miss.total(); + finalResult = finalResult.deleteFace(min); + miss = op.call(miss, parseBinaryArgument(arg, arr, n)); + missNorm = miss && missNorm ? miss.total() / missNorm : 1; + } + let norm = finalResult.total(); + finalResult = op.call(finalResult, arg); + norm = norm ? finalResult.total() / norm : 1; + if (crit) { + const result2 = combineDiceWithNormalization( + crit, + critNorm, + "crit", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (save) { + const result2 = combineDiceWithNormalization( + save, + saveNorm, + "saveHalf", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (miss) { + const result2 = combineDiceWithNormalization( + miss, + missNorm, + "missDamage", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + if (pc) { + const result2 = combineDiceWithNormalization( + pc, + pcNorm, + "pc", + norm, + finalResult + ); + norm = result2.newNorm; + finalResult = result2.updatedResult; + } + op = parseOperation(arr); + } + return finalResult; +} +function parseArgument(s, n) { + let result = parseArgumentInternal(s, n); + while (true) { + const next = parseArgumentInternal(s, n); + if (next === void 0) break; + result = multiplyDiceByDice(result, next); + } + return result; +} +function multiplyDiceByDice(d1, d2) { + if (typeof d1 === "number") d1 = Dice.scalar(d1); + if (typeof d2 === "number") d2 = Dice.scalar(d2); + const result = new Dice(); + const faces = /* @__PURE__ */ new Map(); + let normalizationFactor = 1; + for (const key of d1.keys()) { + let face; + if (typeof key !== "number") { + continue; + } + if (d2.privateData.keep) { + const faceCount = d2.keys().length; + if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { + throw new DiceParseError( + `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` + ); + } + const repeat = Array(key).fill(d2); + face = opDice(repeat, d2.privateData.keep); + } else { + face = multiplyDice(key, d2); + } + normalizationFactor *= face.total(); + faces.set(key, face); + } + for (const [k, face] of faces) { + const count = d1.get(k); + result.combineInPlace( + face.normalize(count * normalizationFactor / face.total()) + ); + } + result.privateData.except = {}; + return result; +} +function multiplyDice(n, d) { + if (n > MAX_DICE_COUNT) { + throw new DiceParseError( + `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` + ); + } + if (n === 0) return new Dice(0); + if (n === 1) return d; + const half = Math.floor(n / 2); + let result = multiplyDice(half, d); + result = result.add(result); + if (n % 2 === 1) { + result = result.add(d); + } + return result; +} +function opDice(diceList, keepFn) { + return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); +} +function opDiceInternal(diceList, result, index, values, weight, combineFn) { + if (index === diceList.length) { + return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); + } + const currentDice = diceList[index]; + for (const face of currentDice.keys()) { + values.push(face); + result = opDiceInternal( + diceList, + result, + index + 1, + values, + weight * currentDice.get(face), + combineFn + ); + values.pop(); + } + return result; +} +function parseArgumentInternal(s, n) { + if (s.length === 0) return; + const c = s[0]; + switch (c) { + case "(": + s.shift(); + return assertToken(s, ")", parseExpression(s, n)); + case "h": + case "d": + return parseDice(s, n); + case "k": + assertToken(s, "k"); + return parseKeep(s, n); + case "n": + return parseNumber(s, n); + default: + if (isDigit(c)) return parseNumber(s, n); + return; + } +} +function parseBinaryArgument(arg, arr, n) { + if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { + assertToken(arr, "half"); + const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; + return diceArg.divideRoundDown(2); + } + const parsed = parseArgument(arr, n); + return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; +} +function assertToken(s, expected, ret) { + for (const ch of expected) { + const found = s.shift(); + if (found !== ch) { + throw new Error(`Expected character '${ch}', found '${found}'`); + } + } + return ret; +} +function parseDice(s, n) { + let rerollOne = false; + if (peek(s, "hd") && peekIsNumber(s, 2)) { + assertToken(s, "h"); + assertToken(s, "d"); + rerollOne = true; + } else if (peek(s, "d") && peekIsNumber(s, 1)) { + assertToken(s, "d"); + } else { + return; + } + const sides = parseNumber(s, n); + if (sides > MAX_DIE_SIDES) { + throw new DiceParseError( + `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` + ); + } + let result = new Dice(sides); + if (rerollOne) { + result = result.reroll(1); + } + return result; +} +function peek(arr, expected) { + if (expected.length > arr.length) return false; + for (let i = 0; i < expected.length; i++) { + if (arr[i] !== expected.charAt(i)) return false; + } + return true; +} +function peekIsNumber(arr, index) { + if (index >= arr.length) return false; + return isDigit(arr[index]) || arr[index] === "n"; +} +function parseNumber(s, n) { + let ret = ""; + while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { + const ch = s.shift(); + ret += ch === "n" ? n.toString() : ch; + } + if (ret.length === 0) { + throw new Error(`Expected number, found: '${s[0]}'`); + } + return parseInt(ret, 10); +} +function isDigit(c) { + return c >= "0" && c <= "9"; +} +function parseKeep(s, n) { + let keepLowest = false; + if (peek(s, "l")) { + assertToken(s, "l"); + keepLowest = true; + } else if (peek(s, "h")) { + assertToken(s, "h"); + keepLowest = false; + } else { + return; + } + const keepCount = parseNumber(s, n); + const result = parseArgumentInternal(s, n); + if (result instanceof Dice) { + result.privateData.keep = keepN(keepCount, keepLowest); + return result; + } + throw new Error("Expected Dice after keep modifier"); +} +function keepN(n, low) { + return (values) => { + const sorted = [...values].sort((a, b) => low ? a - b : b - a); + return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); + }; +} +function parseOperation(s) { + switch (s[0]) { + case ")": + return; + case "a": + assertToken(s, "ac"); + return Dice.prototype.ac; + case "d": + assertToken(s, "dc"); + return Dice.prototype.dc; + case "!": + assertToken(s, "!"); + const adv = Dice.prototype.advantage; + adv.unary = true; + return adv; + case ">": + assertToken(s, ">"); + return Dice.prototype.max; + case "<": + assertToken(s, "<"); + return Dice.prototype.min; + case "+": + assertToken(s, "+"); + return Dice.prototype.addNonZero; + case "~": + assertToken(s, "~"); + assertToken(s, "+"); + return Dice.prototype.add; + case "-": + assertToken(s, "-"); + return Dice.prototype.subtract; + case "&": + assertToken(s, "&"); + return Dice.prototype.combine; + case "r": + assertToken(s, "reroll"); + return Dice.prototype.reroll; + case "*": + assertToken(s, "*"); + if (peek(s, "*")) { + assertToken(s, "*"); + return Dice.prototype.multiply; + } + return Dice.prototype.conditionalApply; + case "/": + assertToken(s, "/"); + if (s[0] === "/") { + assertToken(s, "/"); + return Dice.prototype.divideRoundDown; + } + return Dice.prototype.divideRoundUp; + case "=": + assertToken(s, "="); + return Dice.prototype.eq; + } + return; +} + +// src/pmf/mixture.ts +var Mixture = class _Mixture { + constructor(eps = EPS) { + this.totals = /* @__PURE__ */ new Map(); + // raw mass per outcome (pre-normalization) + this.labelMass = /* @__PURE__ */ new Map(); + this.eps = Number.isFinite(eps) ? eps : EPS; + } + /** Remove all accumulated state. */ + clear() { + this.totals.clear(); + this.labelMass.clear(); + return this; + } + /** Number of distinct outcome values currently accumulated. */ + size() { + return this.totals.size; + } + /** Whether a label was ever added. */ + hasLabel(label) { + for (const bag of this.labelMass.values()) if (bag[label]) return true; + return false; + } + /** + * Add a labeled component with a mixture weight. + * Weight can be any positive finite number. Very small contributions are pruned by eps. + */ + add(label, pmf, weight = 1) { + if (!Number.isFinite(weight) || weight <= 0) return this; + for (const [v, bin] of pmf) { + const p = bin.p; + if (p <= 0) continue; + const add = weight * p; + if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; + this.totals.set(v, (this.totals.get(v) ?? 0) + add); + const bag = this.labelMass.get(v) ?? {}; + bag[label] = (bag[label] ?? 0) + add; + this.labelMass.set(v, bag); + } + return this; + } + buildPMF(eps = EPS) { + let grand = 0; + let c = 0; + for (const m of this.totals.values()) { + const y = m - c; + const t = grand + y; + c = t - grand - y; + grand = t; + } + if (!(grand > 0)) throw new Error("Mixture: zero total mass"); + const internal = /* @__PURE__ */ new Map(); + for (const [v, m] of this.totals) { + if (m <= 0 || Math.abs(m) < this.eps) continue; + const count = this.labelMass.get(v) ?? {}; + internal.set(v, { p: m / grand, count }); + } + return new PMF(internal, eps); + } + /** + * Produce normalized *per-label* PMFs (labels independent). + * These are unlabeled PMFs built from the raw mass of that label alone. + */ + byOutcome() { + const labels = /* @__PURE__ */ new Set(); + for (const bag of this.labelMass.values()) { + for (const k of Object.keys(bag)) labels.add(k); + } + const out = {}; + for (const label of labels) { + const m = /* @__PURE__ */ new Map(); + for (const [v, bag] of this.labelMass) { + const w = bag[label]; + if (w && Math.abs(w) >= this.eps) m.set(v, w); + } + if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); + } + return out; + } + /** + * Mixture weights per label, normalized to sum to 1 over labels that appeared. + * Uses raw mass before per-outcome normalization. + */ + weights() { + const res = {}; + for (const [, bag] of this.labelMass) { + for (const [lab, w] of Object.entries(bag)) { + if (!Number.isFinite(w) || w <= 0) continue; + res[lab] = (res[lab] ?? 0) + w; + } + } + let total = 0; + let c = 0; + for (const v of Object.values(res)) { + const y = v - c; + const t = total + y; + c = t - total - y; + total = t; + } + if (total > 0) { + for (const k in res) res[k] = res[k] / total; + } + return res; + } + toJSON() { + return { + totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), + labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), + eps: this.eps + }; + } + static mix(items, eps = EPS) { + const mix = new _Mixture(eps); + for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); + return mix.buildPMF(); + } +}; + +export { ALL_OUTCOME_TYPES, DiceParseError, DiceQuery, EPS, LRUCache, MISS_NONE_OUTCOME, Mixture, OUTCOME_DISPLAY_ORDER, PMF, calculateBounceOdds, clearParserCache, critProbability, getCachingEnabled, onAnyHit, onCritOnly, onHitOnly, onMissDamageOnly, onMissOnly, onPotentCantripOnly, onSaveFailOnly, onSaveHalfOnly, parse, pmfCache, setCachingEnabled, sortOutcomes }; +//# sourceMappingURL=index.js.map +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..7a1d8bc --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/common/bounce.ts","../src/common/errors.ts","../src/common/lru-cache.ts","../src/common/types.ts","../src/pmf/query.ts","../src/pmf/pmf.ts","../src/parser/dice.ts","../src/parser/parser.ts","../src/pmf/mixture.ts"],"names":["cumulativeSum","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Bounce odds — the \"birthday problem\" for bouncing damage dice (e.g. Chromatic\n * Orb): the probability that at least two of K dice with S faces show the same\n * value, which is what lets the spell jump to another target.\n *\n * Accounts for two modifiers:\n * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are\n * bumped up to it, collapsing the low faces onto a single heavier value.\n * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be\n * rerolled once, giving a second chance at a match.\n *\n * The base and Elemental-Adept cases are computed exactly (see\n * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered\n * on the exact base match probability.\n */\n\n/** Options that modify bounce odds via metamagic / feats. */\nexport interface BounceOddsOptions {\n /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */\n minimumDieRoll?: number;\n /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */\n rerollDamageDice?: number;\n}\n\n/** Binomial coefficient C(n, k), 0 for out-of-range k. */\nfunction binom(n: number, k: number): number {\n if (k < 0 || k > n) return 0;\n let result = 1;\n for (let i = 0; i < k; i++) result = (result * (n - i)) / (i + 1);\n return result;\n}\n\n/**\n * Exact P(all K dice show distinct values) for a die with `uniformCount`\n * ordinary faces (each probability `1/faces`) plus one optional heavy face whose\n * probability is `heavyWeight` (used for the Elemental-Adept collapse; pass 0\n * for a plain die). Uses the elementary symmetric polynomial e_K over the face\n * probabilities: P(all distinct) = K! · e_K.\n */\nfunction pAllDistinct(\n dice: number,\n faces: number,\n uniformCount: number,\n heavyWeight: number\n): number {\n const light = 1 / faces;\n // e_K = (choose K distinct light faces) + (heavy face + K-1 light faces).\n const eK =\n binom(uniformCount, dice) * Math.pow(light, dice) +\n heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1);\n let kFactorial = 1;\n for (let i = 2; i <= dice; i++) kFactorial *= i;\n return kFactorial * eK;\n}\n\n/** Exact P(at least one duplicate) among `dice` dice, honoring Elemental Adept. */\nfunction pMatch(dice: number, faces: number, minimumDieRoll: number): number {\n if (dice <= 1) return 0;\n if (dice > faces) return 1;\n\n if (minimumDieRoll >= 2) {\n // Rolls 1..minimumDieRoll collapse onto the value `minimumDieRoll`, giving it\n // weight minimumDieRoll/faces; the faces above it stay uniform at 1/faces.\n const uniformCount = faces - minimumDieRoll; // values minimumDieRoll+1 .. faces\n const effectiveValues = uniformCount + 1; // + the collapsed value\n if (dice > effectiveValues) return 1;\n const heavyWeight = minimumDieRoll / faces;\n const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight);\n return Math.min(1, Math.max(0, 1 - distinct));\n }\n\n // Plain die: P(all distinct) = falling_factorial(faces, dice) / faces^dice.\n let pDistinct = 1;\n for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces;\n return 1 - pDistinct;\n}\n\n/**\n * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring\n * Elemental Adept and Empowered Spell. Returns a probability in [0, 1].\n *\n * @param diceCount Number of dice rolled.\n * @param dieFaces Faces per die (e.g. 8 for d8).\n * @param options Optional metamagic / feat modifiers.\n */\nexport function calculateBounceOdds(\n diceCount: number,\n dieFaces: number,\n options?: BounceOddsOptions\n): number {\n if (diceCount <= 1) return 0;\n if (diceCount > dieFaces) return 1; // pigeonhole\n\n const minimumDieRoll = options?.minimumDieRoll ?? 0;\n const rerollDamageDice = options?.rerollDamageDice ?? 0;\n\n const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll);\n\n // Without Empowered Spell we're done.\n const rerollCount = Math.min(rerollDamageDice, diceCount);\n if (rerollCount <= 0) return pMatchFirst;\n\n // Empowered Spell: reroll `rerollCount` non-matching dice once. Model the\n // second chance as (a rerolled die matching one of the kept dice) OR (the\n // rerolled dice matching among themselves).\n const pNoMatchFirst = 1 - pMatchFirst;\n const keptDice = diceCount - rerollCount;\n const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces;\n\n // With no kept dice, a rerolled die vacuously \"misses\" all of them (prob 1), so\n // the only way to match is among the rerolled dice themselves (pRerolledMatch below).\n const pRerollDieMissesAll =\n keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1;\n const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll;\n const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0;\n const pMatchAfterReroll = Math.min(\n 1,\n pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches)\n );\n\n return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll);\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n"]} \ No newline at end of file diff --git a/dist/pmf-D5VRghZI.d.cts b/dist/pmf-D5VRghZI.d.cts new file mode 100644 index 0000000..a851fe5 --- /dev/null +++ b/dist/pmf-D5VRghZI.d.cts @@ -0,0 +1,1129 @@ +/** + * Simple LRU cache implementation + */ +declare class LRUCache { + private readonly maxSize; + private cache; + constructor(maxSize?: number); + get(key: K): V | undefined; + delete(key: K): void; + set(key: K, value: V): this; + clear(): void; + get size(): number; + has(key: K): boolean; + keys(): IterableIterator; + values(): IterableIterator; +} + +/** Mapping from outcome label to probability mass or damage attribution. */ +type OutcomeLabelMap = Partial>; +/** Computational epsilon for pruning negligible probabilities. */ +declare const EPS = 1e-12; +/** A probability bin for a specific damage value. */ +interface Bin { + /** Total probability mass at this damage value. */ + p: number; + /** Per-outcome probability mass contributions at this damage. */ + count: OutcomeLabelMap; + /** Optional per-outcome damage attribution at this damage. */ + attr?: OutcomeLabelMap; +} +interface CritConfig { + critThreshold: number; +} +/** Simple mapping from damage value to probability. */ +type DamageDistribution = Record; +/** Canonical outcome labels supported by the query helpers. */ +type OutcomeType = "crit" | "hit" | "missNone" | "missDamage" | "saveHalf" | "saveFail" | "pc"; +type Rounding = "none" | "floor" | "round" | "ceil"; +/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */ +type RollType = "flat" | "advantage" | "disadvantage" | "elven accuracy"; +/** + * P(critical hit) for the given crit window and d20 {@link RollType}. + * + * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for + * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage + * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps + * the worst of two. + */ +declare function critProbability(critRange: number, rollType?: RollType): number; +/** + * The canonical "clean miss" outcome — a point of zero damage with no rider. + * This is the {@link OutcomeType} that attribution charts and outcome stats key + * on, and is distinct from the builder's attack-resolution `miss` weight label. + */ +declare const MISS_NONE_OUTCOME: OutcomeType; +/** + * All outcome types in canonical severity order — clean miss → crit. This is + * also the natural stacking order for attribution charts (least- to + * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly + * once; use it instead of hand-maintained per-consumer outcome tables. + */ +declare const ALL_OUTCOME_TYPES: OutcomeType[]; +/** + * Outcome types in display order for stats / breakdown rows — most prominent + * first (crit, hit, …) down to the clean miss. + */ +declare const OUTCOME_DISPLAY_ORDER: OutcomeType[]; +/** + * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}). + * Labels not present in `order` sort after known ones, alphabetically — so + * ad-hoc/test labels outside the {@link OutcomeType} union stay stable. + */ +declare function sortOutcomes(outcomes: Iterable, order?: readonly string[]): T[]; +declare const onAnyHit: OutcomeType[]; +declare const onCritOnly: OutcomeType[]; +declare const onHitOnly: OutcomeType[]; +declare const onMissOnly: OutcomeType[]; +declare const onMissDamageOnly: OutcomeType[]; +declare const onSaveHalfOnly: OutcomeType[]; +declare const onSaveFailOnly: OutcomeType[]; +declare const onPotentCantripOnly: OutcomeType[]; + +/** + * Query interface for analyzing dice roll probability distributions. + * + * Combines multiple attack PMFs and provides statistical analysis methods for: + * - Basic statistics (mean, variance, min/max, percentiles) + * - Probability queries (hit chances, success rates, exact counts) + * - Damage analysis (ranges by outcome type, expected values) + * - Data export (charts, tables, visualizations) + * + */ +declare class DiceQuery { + readonly singles: PMF[]; + private readonly _eps; + private readonly _combinedProvided; + private _combined?; + private _combinedWithAttr?; + constructor(singles: PMF | PMF[], combined?: PMF, eps?: number); + /** + * The combined damage distribution of all single PMFs (their convolution), + * normalized to total probability 1. + * + * Computed lazily on first access and cached. Queries that only need + * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, + * {@link DiceQuery.stddev} — never trigger this convolution. + */ + get combined(): PMF; + private static readonly DEFAULT_OUTCOMES; + /** + * Returns a new PMF with damage attribution metadata populated. + * + * This method computes attribution on-demand for builder-generated PMFs, + * enabling them to work with damage attribution charts. The `attr` field + * tracks how much damage each outcome type contributes at each damage value. + * + * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) + * + * Performance: Cached after first call. Adds minimal overhead vs `combined`. + * + * @returns PMF with attr field populated for damage attribution charts + * + * @example + * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) + * const query = attack.toQuery() + * const pmf = query.combinedWithAttribution() + * // Now pmf can be used with toDamageAttributionChartSeries() + */ + combinedWithAttribution(): PMF; + /** + * Per-label `damage value → probability mass` series for the combined, + * attribution-carrying distribution — the provenance core of the stacked + * damage-attribution chart. Convenience for + * `combinedWithAttribution().attributionByValue()`; see + * {@link PMF.attributionByValue}. + */ + attributionByValue(): Map>; + /** + * How many of the independent single PMFs can produce the given outcome + * label. Useful for "all of them succeeded" style probabilities where the + * exponent is the number of contributing attacks (see + * {@link DiceQuery.probExactlyK}). + */ + countSinglesWith(label: string): number; + /** + * Returns the expected damage across all possible outcomes. + * + * Example: `query.mean()` → 12.5 + * Use case: "What's my average damage per round?" + */ + mean(): number; + /** + * Returns the variance of the damage distribution. + * + * Example: `query.variance()` → 45.2 + * Use case: "How much does my damage vary from the average?" + * High variance means higher risk/reward. Lower variance means more consistent damage. + */ + variance(): number; + /** + * Returns the standard deviation of the damage distribution. + * + * Example: `query.stdev()` → 6.7 + * Use case: "What's the typical spread around my average damage?" + * Used to determine how consistent the damage is. + */ + stddev(): number; + /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ + stdev(): number; + /** + * Returns the Cumulative Distribution Function. + */ + cdf(x: number): number; + /** + * Returns the probability of dealing X damage or less. + * In statistics, this is called the cumulative distribution function (CDF). + * Example: `query.cdf(20)` → 0.75 + * Use case: "What's the chance I deal 20 damage or less?" + */ + probTotalAtMost(x: number): number; + /** + * Returns the Complementary Cumulative Distribution Function. + */ + ccdf(x: number): number; + /** + * Returns the probability of dealing at least X damage. + * + * Example: `query.probTotalAtLeast(25)` → 0.35 + * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" + */ + probTotalAtLeast(threshold: number): number; + /** + * Returns damage values at specific percentiles. + * + * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] + * Use case: "What are my 25th, 50th, and 75th percentile damage values?" + */ + percentiles(percentileValues: number[]): number[]; + /** + * Returns the minimum possible damage. + * + * Example: `query.min()` → 0 + * Use case: "What's the worst-case damage if everything misses?" + */ + min(): number; + /** + * Returns the maximum possible damage. + * + * Example: `query.max()` → 56 + * Use case: "What's the best-case damage if everything crits and rolls max?" + */ + max(): number; + private singleProb; + /** + * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it + * carries ANY of `labels`", over the n independent singles. + * + * Each single's per-event success probability is the Poisson-binomial + * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), + * computed exactly once. The binomial DP then runs once to produce the whole + * distribution, so the array-label paths of probExactlyK / probAtLeastK / + * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and + * re-running the DP per requested k. + */ + private countDistribution; + probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number; + /** + * Returns the probability that at least one attack has the specified outcome(s). + * - This is the complement of probAtMostK(labels, 0) + * + * Examples: + * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) + * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) + * + * Use cases: + * - "What's the chance at least one of my attacks connects?" + * + * Note: + * + * - You have to pass in an array of labels to avoid double-counting if you are + * using multiple labels. You cannot just add them. + */ + probAtLeastOne(labels: OutcomeType | OutcomeType[]): number; + /** + * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. + * + * Uses dynamic programming to efficiently calculate the probability distribution + * of how many attacks will have the specified outcome, accounting for different + * success probabilities across individual attacks. + * + * Example: For 3 attacks with 50% hit chance each, returns: + * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] + * + * @param label - The outcome type to count + * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) + * @returns Array where index K contains P(exactly K attacks have the label) + */ + private computeBinomialProbabilities; + /** + * Returns the probability that exactly K attacks result in the specified outcome(s). + * + * Single label examples: + * - probExactlyK('hit', 2) = probability exactly 2 attacks hit + * - probExactlyK('crit', 1) = probability exactly 1 attack crits + * - probExactlyK('crit', 0) = probability no attacks crit + * + * Array examples: + * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed + * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds + * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss + * + * Use cases: + * - "What's the chance exactly one of my attacks hits?" + * - "How likely am I to get exactly 2 successes out of 3 attacks?" + * - "What's the probability that exactly half my attacks succeed?" + * + * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. + * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. + */ + probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number; + /** + * Returns the probability that AT MOST K attacks result in the specified outcome(s). + * + * Single label examples: + * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) + * - probAtMostK('crit', 0) = probability no attacks crit + * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss + * + * Array examples: + * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds + * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) + * + * Use cases: + * - "What's the chance that at most one attack hits?" (rest miss) + * - "How likely am I to have mostly failures?" (at most 1 success) + * - "What's the probability of a really bad turn?" (at most 0 successes) + * + */ + probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number; + /** + * Returns the expected damage attributed to specific outcome types. + * + * Single label examples: + * - expectedDamageFrom('hit') = expected damage from hit components + * - expectedDamageFrom('crit') = expected damage from crit components + * + * Array examples: + * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success + * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses + * + * Use cases: + * - "How much damage do I expect from successful attacks?" + * - "What's the damage contribution from critical hits specifically?" + * - "How much damage comes from miss effects (like save-for-half spells)?" + */ + expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number; + /** + * Returns damage statistics for scenarios where AT LEAST ONE attack results in + * the specified outcome(s). + * + * This method answers "What happens when things go reasonably well?" rather than + * "What's the theoretical maximum?" It includes mixed scenarios which are more + * common and tactically relevant than pure scenarios. + * + * Single label examples: + * - damageStatsFrom('hit') = damage range when at least one attack hits + * - damageStatsFrom('crit') = damage range when at least one attack crits + * + * Array examples: + * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds + * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses + * + * Tactical Use Cases: + * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" + * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" + * - "Should I use this risky spell if it has good damage when it works?" + * - "What's my damage potential when something goes right?" (vs pure failure) + * + * Combat Planning Examples: + * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" + * (Much more useful than "You average 50 damage including complete misses") + * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" + * - Resource management: "If I hit anything, I'll likely finish this enemy" + * + * Statistical Note: + * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which + * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. + * + * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an + * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and + * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than + * E[dmg | the label occurs]. For a single attack both are the plain + * conditional figures. Use {@link probAtLeastOne} for the scenario probability. + * + * @example + * // High-level tactical planning + * const successStats = query.damageStatsFrom('hit') + * const successChance = query.probAtLeastOne('hit') + * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) + */ + damageStatsFrom(labels: OutcomeType | OutcomeType[]): { + min: number; + max: number; + avg: number; + count: number; + }; + /** + * Returns damage statistics for scenarios where ALL attacks result in the specified + * outcome, calculated by leveraging the pure partition of singles. + * + * This method answers "What's the theoretical best/worst case?" and "What are the + * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. + * + * Examples: + * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) + * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) + * + * UI and Display Use Cases: + * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) + * - "Best case scenario" vs "worst case scenario" analysis + * - Mathematical verification: "Does our hit damage calculation match manual math?" + * - Clean damage type attribution: "How much comes from base hits vs crits?" + * + * Design and Balance Use Cases: + * - Game designers: "What's the damage ceiling if someone gets lucky?" + * - Character optimization: "What's my absolute maximum potential?" + * - Ability comparison: "Which build has higher crit ceiling?" + * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" + * + * Mathematical Use Cases: + * - Validating complex calculations against simple manual math + * - Understanding damage component contributions in isolation + * - Separating luck (crit variance) from consistency (hit variance) + * - Building intuition about damage sources + * + * When to Use This vs damageStatsFrom(): + * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons + * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios + * + * Statistical Note: + * Pure scenarios (all hits, all crits) are rare but represent clear mathematical + * boundaries. These stats help understand the "shape" of your damage potential. + * + * @example + * // UI display logic + * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" + * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" + * + * // vs tactical planning (use damageStatsFrom instead) + * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios + */ + combinedDamageStats(targetLabel: OutcomeType): { + min: number; + max: number; + avg: number; + count: number; + }; + /** + * Returns the probability that at least one attack carries ANY of the + * specified labels (the marginal P(≥1) across the independent attacks). + * + * Examples: + * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) + * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) + * + * Use cases: + * - "What's the chance my resolution includes a success label?" + * - "How likely am I to get any hits or crits across all attacks?" + * + * Note: this must NOT be computed by summing `combined` bin probabilities. A + * single combined damage total is reachable by many outcome combinations and + * a bin can hold several labels at once, so summing `bin.p` over bins that + * contain a label over-counts. The correct marginal is the Poisson-binomial + * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. + */ + probabilityOf(labels: OutcomeType | OutcomeType[]): number; + /** + * Returns the probability of missing (any type of miss). + * + * Example: `query.missChance()` → 0.04 + * Use case: "What's the chance I miss completely this turn?" + */ + missChance(): number; + /** + * Returns data formatted for plotting damage probability distribution. + * + * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] + * Use case: "I want to visualize my damage distribution in a chart." + */ + toChartSeries(): Array<{ + x: number; + y: number; + }>; + /** + * Returns tabular data showing damage values and their probability breakdowns. + * + * Example: `query.toLabeledTable(['hit', 'crit'])` → + * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] + * + * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." + */ + toLabeledTable(labels?: OutcomeType[]): Array<{ + damage: number; + total: number; + } & Record>; + /** + * Returns data for stacked charts with unconditional per-label probability mass per damage. + * + * - Each dataset value equals the unconditional probability mass for that label at that damage + * (i.e., `bin.count[label]`). + * - Column sums may be less than the total probability `bin.p` when you omit labels or when + * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. + * - This behavior matches tests that expect raw per-label mass (not proportional scaling). + * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. + * + * @example + * query.toStackedChartData(['hit', 'crit']) + * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} + */ + toStackedChartData(labels?: OutcomeType[], epsilon?: number): { + labels: number[]; + datasets: Array<{ + label: string; + data: number[]; + }>; + }; + /** + * Returns pure mathematical data for attribution charts showing outcome contributions. + * + * Automatically discovers all outcome types present in the PMF, applies filtering rules, + * and returns proportional data suitable for stacked visualization. + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and proportional data + * + * @example + * query.toAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} + */ + toAttributionChartSeries(options?: { + stackOrder?: string[]; + filterRules?: (outcome: string, damage: number) => boolean; + asPercentages?: boolean; + }): { + support: number[]; + outcomes: string[]; + data: { + [outcome: string]: number[]; + }; + }; + /** + * Returns pure mathematical data for damage attribution charts showing damage contribution + * from each outcome type at each damage value. + * + * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of + * bin.count (probability attribution). + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) + * @returns Pure data structure with support, outcomes, and damage attribution data + * + * @example + * query.toDamageAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} + */ + toDamageAttributionChartSeries(options?: { + stackOrder?: string[]; + filterRules?: (outcome: string, damage: number) => boolean; + asPercentages?: boolean; + }): { + support: number[]; + outcomes: string[]; + data: { + [outcome: string]: number[]; + }; + }; + /** + * Returns pure mathematical data for outcome attribution charts showing which + * attack outcome combinations can produce each damage value. + * + * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks + * outcome combinations - answering "what attack outcomes produced this damage?" + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and outcome combination probabilities + * + * @example + * query.toOutcomeAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} + */ + toOutcomeAttributionChartSeries(options?: { + stackOrder?: string[]; + filterRules?: (outcome: string, damage: number) => boolean; + asPercentages?: boolean; + }): { + support: number[]; + outcomes: string[]; + data: { + [outcome: string]: number[]; + }; + }; + /** + * Returns pure mathematical data for cumulative distribution function (CDF). + * Shows P(X ≤ x) - the probability of getting at most x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and cumulative probabilities + * + * @example + * query.toCDFSeries() + * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} + */ + toCDFSeries(asPercentages?: boolean): { + support: number[]; + data: number[]; + }; + /** + * Returns pure mathematical data for complementary cumulative distribution function (CCDF). + * Shows P(X ≥ x) - the probability of getting at least x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and complementary cumulative probabilities + * + * @example + * query.toCCDFSeries() + * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} + */ + toCCDFSeries(asPercentages?: boolean): { + support: number[]; + data: number[]; + }; + /** Probability of doing strictly more than threshold damage (default >0). */ + probDamageGreaterThan(threshold?: number): number; + /** All outcome keys actually present (typed & ordered if you pass an order). */ + outcomeKeys(order?: OutcomeType[]): OutcomeType[]; + /** Total probability per outcome across the PMF. */ + outcomeTotals(outcomes?: OutcomeType[]): Map; + /** Conditional damage range per outcome (min/avg/max of X | outcome). */ + outcomeDamageRanges(outcomes?: OutcomeType[]): Map; + /** + * Snapshot of the distribution in the exact shape the UI consumes. + * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) + * - damageRange is conditional on the outcome occurring + * + * The outcome probabilities use the correct Poisson-binomial marginals + * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), + * so they are always valid probabilities in [0,1]. + * + * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from + * the combined PMF's `count`, which the convolution accumulates as an EXPECTED + * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] + * rather than a clean conditional expectation. It is correct for a single + * attack. + */ + snapshot(order?: readonly OutcomeType[]): Snapshot; + /** + * PMF Transformation Methods + * + * These methods provide a fluent API for transforming dice queries by wrapping + * the underlying PMF transformation methods. All operations work on the combined + * PMF and return new DiceQuery instances. + */ + /** + * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). + * + * @returns New DiceQuery with normalized combined PMF + */ + normalize(): DiceQuery; + /** + * Returns a new DiceQuery with low-probability outcomes removed. + * + * @param eps Minimum probability threshold (defaults to PMF epsilon) + * @param keepFinalBin Whether to keep the highest damage bin regardless of probability + * @returns New DiceQuery with compacted combined PMF + */ + compact(eps?: number, keepFinalBin?: boolean): DiceQuery; + /** + * Returns a new DiceQuery with an additional scaled branch added. + * Useful for conditional outcomes like "30% chance of opportunity attack". + * + * @param branch DiceQuery to add as a scaled branch + * @param probability Probability of the branch occurring (0-1) + * @returns New DiceQuery combining this query with the scaled branch + * + * @example + * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); + * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); + */ + addScaled(branch: DiceQuery, probability: number): DiceQuery; + /** + * Returns a new DiceQuery with all probabilities scaled by a factor. + * Used for conditional scenarios where the entire outcome has reduced probability. + * + * @param factor Scaling factor for probabilities + * @returns New DiceQuery with scaled probabilities + * + * @example + * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario + */ + scaleMass(factor: number): DiceQuery; + totalMass(): number; + /** + * Returns a new DiceQuery with damage values transformed by a function. + * Useful for applying modifiers, resistances, or other damage transformations. + * + * @param damageTransformFunction Function to transform each damage value + * @returns New DiceQuery with transformed damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage + * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage + */ + mapDamage(damageTransformFunction: (damageValue: number) => number): DiceQuery; + /** + * Returns a new DiceQuery with damage values scaled by a factor. + * Convenient wrapper around mapDamage for multiplicative scaling. + * + * @param factor Scaling factor for damage values + * @param rounding Rounding method: "floor" (default), "round", or "ceil" + * @returns New DiceQuery with scaled damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const doubled = baseAttack.scaleDamage(2); // Double damage + * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded + */ + scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): DiceQuery; + /** + * Returns a new DiceQuery combining this query with another via convolution. + * Equivalent to rolling both queries independently and adding results. + * It is important to use this rather than combing()ing the PMFs directly! + * This method maintains the provenance of the PMFs which is needed for damage attribution. + * Combining the .combined PMFs directly is still valid for DPR calculations but + * is not statistically sound for queries. + * + * @param other DiceQuery to combine with + * @param eps Optional epsilon for precision control + * @returns New DiceQuery representing the combined outcome + * + * @example + * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); + * const bothAttacks = mainAttack.convolve(bonusAttack); + */ + convolve(other: DiceQuery): DiceQuery; + /** + * First-success split over an ordered list of DISTINCT single-swing PMFs. + * Each PMF may have different success/subset probabilities (from labels). + * + * successOutcome: e.g., ["success"] or ["hit", "crit"] + * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success + * + * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] + */ + firstSuccessSplit(successOutcome: OutcomeType | OutcomeType[], subsetOutcome: OutcomeType | OutcomeType[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number]; +} +type OutcomeSnapshot = { + atLeastOneProbability: number; + allProbability: number; + damageRange: { + min: number; + avg: number; + max: number; + }; +}; +type Snapshot = { + averageDPR: number; + damageChance: number; + percentiles: { + p25: number; + p50: number; + p75: number; + }; + outcomes: Map; +}; + +declare const pmfCache: LRUCache; +/** + * Probability Mass Function for discrete damage distributions. + */ +declare class PMF { + readonly map: Map; + readonly epsilon: number; + readonly normalized: boolean; + readonly identifier: string; + private _preservedProvenance; + private static __anonIdCounter; + private _support?; + private _min?; + private _max?; + private _totalMass?; + private _mean?; + private _variance?; + private _stdev?; + private _fingerprint?; + constructor(map?: Map, epsilon?: number, normalized?: boolean, identifier?: string, _preservedProvenance?: boolean); + static empty(epsilon?: number, identifier?: string): PMF; + static zero(epsilon?: number): PMF; + static delta(value: number, epsilon?: number): PMF; + /** + * Point mass at damage 0 tagged with the canonical `missNone` outcome. + * + * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the + * builder's attack-resolution vocabulary. This uses the `missNone` + * {@link OutcomeType} that the attribution charts and outcome stats key on, + * so it is the correct "clean miss / no damage" delta for provenance-aware + * mixtures feeding those consumers. + */ + static missNone(epsilon?: number): PMF; + static emptyMass(): PMF; + [Symbol.iterator](): IterableIterator<[number, Bin]>; + static clearCache(): void; + /** + * Creates a conditional PMF from two branches (success and failure) and a probability. + * This is the core logic for modeling any probabilistic event where there are two + * distinct outcomes. + */ + static branch(successPMF: PMF, failurePMF: PMF, successProbability: number): PMF; + /** + * withProbability() + * + * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). + * + * Think of this as a shortcut for: + * pmf.gate(p, PMF.zero()) + * + * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, + * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. + * + * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. + * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. + * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) + * use query.firstSuccessSplit() to get the exact probabilities. + * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). + * + */ + static withProbability(successPMF: PMF, probability: number): PMF; + /** + * gate() + * + * A conditional wrapper around branch() that applies this PMF with probability `p`, + * and applies a provided fallback PMF otherwise. + * + * This is useful for modeling a binary choice between two outcomes: + * - The "success" outcome (this PMF) happens with probability `p`. + * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. + * + * Examples: + * - 25% chance to include an opportunity attack, otherwise nothing: + * attackPMF.gate(0.25, PMF.zero()) + * + * - 50% chance to deal fireball damage, otherwise cone of cold damage: + * fireballPMF.gate(0.5, coneOfColdPMF) + * + * Relationship to other helpers: + * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. + * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). + * + * @param p Probability of applying this PMF (between 0 and 1). + * @param fallback PMF to apply when this PMF is *not* selected. + * @returns A new PMF representing the weighted mixture of this PMF and the fallback. + */ + gate(p: number, fallback: PMF): PMF; + /** + * PMF.exclusive() + * + * Builds a single PMF from a set of mutually exclusive weighted outcomes. + * Exactly one of the provided options will occur. + * + * Each option has: + * - A PMF representing its outcome (e.g., damage dice). + * - A weight representing its probability of being selected. + * + * Notes: + * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for floating point rounding. + */ + static exclusive(options: Array<{ + pmf: PMF; + weight: number; + } | [PMF, number]>, eps?: number): PMF; + /** + * PMF.mix() + * + * Builds a PMF as a linear combination of input PMFs with the given weights. + * Unlike `exclusive`, this does NOT: + * - enforce that weights sum to 1 + * - add leftover probability to δ0 (PMF.zero()) + * + * Use when outcomes are not mutually exclusive, or for interpolation/blending. + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for skipping tiny weights. + */ + static mix(options: Array<{ + pmf: PMF; + weight: number; + } | [PMF, number]>, eps?: number): PMF; + /** + * Adds damage attribution metadata to this PMF based on existing count metadata. + * For each bin, sets attr[outcome] = damage × count[outcome]. + * + * This enables damage attribution charts to work with builder-generated PMFs. + * The parser generates attr automatically, but builder PMFs only have count. + * + * @returns New PMF with attr field populated in each bin + */ + /** + * Returns true if this PMF already carries damage attribution metadata. + * + * Only the first positive-damage bin is inspected (parser-generated PMFs + * populate `attr` uniformly), so this is O(1) in practice. + */ + hasAttribution(): boolean; + withAttribution(): PMF; + /** + * General-purpose N-way mixture. + * weights: Array of [weight, PMF]. + * + * Example: PMF.mixN([ + * [pMiss, zero], + * [pHit, hitPMF], + * [pCrit, critPMF], + * ]); + */ + static mixN(weights: [number, PMF][], eps?: number): PMF; + private setPreservedProvenance; + preservedProvenance(): boolean; + private getPowerCacheKey; + /** + * Efficiently computes this PMF convolved with itself `n` times. + * Uses exponentiation by squaring to reduce total convolutions. + * n must be a positive integer. + * * + * * NOTE: This folds multiple independent attacks into a single PMF. + * As a result, The power() method causes a loss of data provenance. + * This is ONLY SAFE if you are trying to calculate masses. + * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). + */ + power(n: number, eps?: number): PMF; + replicate(n: number): PMF[]; + mass(): number; + outcomeMass(outcome: string): number; + faceTotal(): number; + normalize(): PMF; + /** + * Returns a copy with negligible probabilities removed (p < eps). + * If keepFinalBin is true, the bin with the largest key is always kept, + * even if its probability is below eps. count/attr submaps are still cleaned. + */ + compact(eps?: number, keepFinalBin?: boolean): PMF; + support(): number[]; + min(): number; + max(): number; + /** + * Returns the expected (mean) damage value. + * Cached for performance since this requires iterating through all bins. + */ + mean(): number; + /** + * Returns the variance of the damage distribution. + * Cached for performance since this requires mean calculation plus iteration. + */ + variance(): number; + /** + * Returns the standard deviation of the damage distribution. + */ + stdev(): number; + /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ + private static cloneBin; + /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ + private static scaleBin; + private static mergeInto; + add(other: PMF): PMF; + /** + * Returns a new PMF with a scaled branch added to this one. + * The branch PMF is scaled by the given probability before merging + * This will be very useful for conditional effects and for being + * able to model "I can probably have this opportunity attack 40% of rounds" + * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes + */ + addScaled(branch: PMF, probability: number): PMF; + /** + * Redistributes probability mass to model an effect that only occurs with + * probability `frequency` — a conditional attack, an on-hit rider, or a + * sub-one AoE target fraction. + * + * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, + * per-label `count`, AND per-label `attr` — and the freed mass is moved into + * the miss bin at damage 0, tagged with the canonical `missNone` outcome. + * Total probability mass is preserved. + * + * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage + * attribution (`attr`) intact, so a frequency-scaled PMF still renders + * correctly in the damage-attribution charts. + * + * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` + * collapses all mass into the miss bin. The miss outcome is assumed to be + * encoded at damage value 0. + * + * @param frequency Probability in [0, 1] that the effect occurs. + */ + applyHitFrequency(frequency: number): PMF; + scaleMass(factor: number): PMF; + mapDamage(damageTransformFunction: (damageValue: number) => number): PMF; + scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): PMF; + private getPMFCombineCacheKey; + /** + * A small content fingerprint (mass + bin count + face sum) so convolution + * cache keys change if the underlying numbers do. Memoized because a PMF is + * immutable once constructed — this avoids re-summing every key on each + * convolve() call (including cache hits). + */ + fingerprint(): string; + convolve(other: PMF, eps?: number, raw?: boolean): PMF; + combineRaw(other: PMF, eps?: number): PMF; + private static reduceConvolveLeft; + /** + * Convolves multiple PMFs using linear convolution with automatic caching. + * Uses a left-to-right accumulation approach for maximum cache reuse. + * Each convolve() call automatically uses the convolution cache for performance. + * + * This linear approach provides better cache hits than pairwise because: + * - Intermediate results are more predictable and stable + * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) + * - Order-independent cache keys work better with consistent build patterns + */ + static convolveMany(pmfList: PMF[], eps?: number): PMF; + /** + * Returns a plain, JSON-serializable representation of this PMF. + * + * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces + * the expected output (no double-encoding). Use {@link PMF.fromJSON} to + * reconstruct, or {@link PMF.toJSONString} if you need the string directly. + */ + toJSON(): { + bins: Array<[number, Bin]>; + normalized: boolean; + identifier: string; + }; + /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ + toJSONString(): string; + static fromJSON(jsonData: { + bins: Array<[number, Bin]>; + normalized?: boolean; + identifier?: string; + }): PMF; + /** + * Relative pruning with optional top-K floor. + * Keeps bins with p >= epsRel * peak, always keeps min and max damage, + * optionally guarantees at least `minBins` survivors by adding top-K. + * Returns a new, non-normalized PMF. + */ + prune(epsRel: number, minBins?: number): PMF; + /** Probability mass at exactly x. */ + pAt(x: number): number; + /** + * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. + * Assumes a miss is encoded as the damage-0 bin (the convention used across + * attack/save PMFs). The dual of {@link missProbability}. + */ + hitProbability(): number; + /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ + missProbability(): number; + /** + * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width + * damage buckets, aggregating probability mass (and `count`/`attr` + * provenance) into each bucket's start value. Returns this PMF unchanged when + * its integer support already fits within `maxBuckets`. + * + * This is a lossy display/downsampling transform (bucket start replaces the + * exact damage value) — use it for charting wide distributions, not for DPR + * math. + */ + rebin(maxBuckets: number): PMF; + /** Dense integer support from min..max (inclusive). + * Useful for showing empty bars in charts. + */ + denseSupport(): number[]; + /** CDF at x: P(X ≤ x). */ + cdfAt(x: number): number; + /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ + quantile(p: number): number; + /** Get outcome probability at specific damage value. */ + outcomeAt(damage: number, outcome: string): number; + /** Get all outcome types present in this PMF. */ + outcomes(): string[]; + /** Get total probability of an outcome across all damage values. */ + outcomeProbability(outcome: string): number; + /** Get damage attribution for an outcome at specific damage value. */ + outcomeAttributionAt(damage: number, outcome: string): number; + /** Get all outcome data at specific damage value. */ + binAt(damage: number): { + p: number; + count: Record; + attr?: Record; + } | null; + /** Check if outcome exists in this PMF. */ + hasOutcome(outcome: string): boolean; + /** + * Split each damage value's probability mass across outcome labels, returning + * per-label maps of `damage value → probability mass attributable to that + * label`. Summing over labels at a given value recovers that value's `p`. + * + * Damage-bearing bins are split by `attr` weight (the share of damage each + * outcome contributed); the clean-miss bin at 0 is split by `count` weight + * (there is no damage to attribute). Attribution is computed on demand via + * {@link withAttribution} when absent, so builder-generated PMFs work too. + * + * This is the provenance core of the stacked damage-attribution chart — the + * caller only maps these series into its rendering format (colors, binning, + * axis labels). + */ + attributionByValue(): Map>; + tailProbGE(t: number): number; + tailProbGT(t: number): number; + /** + * Returns a new PMF containing only bins where the specified outcome has non-zero probability. + * This creates a marginal distribution for the given outcome type, with probabilities + * scaled to represent the unconditional mass attributable to that outcome. + */ + filterOutcome(outcome: string): PMF; + /** + * Calculates probabilities for first-success outcomes across n independent attempts. + * + * @param pSuccess - Total probability of any success on a single attempt. + * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). + * @param n - Number of independent attempts. + * + * Returns: + * - pSpecificSuccess: Probability that the first success was of the "special" type + * - pGeneralSuccess: Probability that the first success was of the non-special type + * - pNone: Probability that no successes occurred + * - pAny: Probability that at least one success occurred + */ + static firstSuccessWeights(pSuccess: number, pSpecial: number, n: number): { + pSpecificSuccess: number; + pGeneralSuccess: number; + pNone: number; + pAny: number; + }; + mapValues(f: (v: number) => number, eps?: number, opts?: { + rounding?: Rounding; + preserveCounts?: boolean; + }): PMF; + static fromMap(m: Map, eps?: number, { requireIntegerValues }?: { + requireIntegerValues?: boolean; + }): PMF; + query(): DiceQuery; +} + +export { ALL_OUTCOME_TYPES as A, type Bin as B, type CritConfig as C, type DamageDistribution as D, EPS as E, LRUCache as L, MISS_NONE_OUTCOME as M, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type OutcomeType as a, type RollType as b, critProbability as c, OUTCOME_DISPLAY_ORDER as d, onCritOnly as e, onHitOnly as f, onMissOnly as g, onMissDamageOnly as h, onSaveHalfOnly as i, onSaveFailOnly as j, onPotentCantripOnly as k, DiceQuery as l, type OutcomeSnapshot as m, onAnyHit as o, pmfCache as p, sortOutcomes as s }; diff --git a/dist/pmf-D5VRghZI.d.ts b/dist/pmf-D5VRghZI.d.ts new file mode 100644 index 0000000..a851fe5 --- /dev/null +++ b/dist/pmf-D5VRghZI.d.ts @@ -0,0 +1,1129 @@ +/** + * Simple LRU cache implementation + */ +declare class LRUCache { + private readonly maxSize; + private cache; + constructor(maxSize?: number); + get(key: K): V | undefined; + delete(key: K): void; + set(key: K, value: V): this; + clear(): void; + get size(): number; + has(key: K): boolean; + keys(): IterableIterator; + values(): IterableIterator; +} + +/** Mapping from outcome label to probability mass or damage attribution. */ +type OutcomeLabelMap = Partial>; +/** Computational epsilon for pruning negligible probabilities. */ +declare const EPS = 1e-12; +/** A probability bin for a specific damage value. */ +interface Bin { + /** Total probability mass at this damage value. */ + p: number; + /** Per-outcome probability mass contributions at this damage. */ + count: OutcomeLabelMap; + /** Optional per-outcome damage attribution at this damage. */ + attr?: OutcomeLabelMap; +} +interface CritConfig { + critThreshold: number; +} +/** Simple mapping from damage value to probability. */ +type DamageDistribution = Record; +/** Canonical outcome labels supported by the query helpers. */ +type OutcomeType = "crit" | "hit" | "missNone" | "missDamage" | "saveHalf" | "saveFail" | "pc"; +type Rounding = "none" | "floor" | "round" | "ceil"; +/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */ +type RollType = "flat" | "advantage" | "disadvantage" | "elven accuracy"; +/** + * P(critical hit) for the given crit window and d20 {@link RollType}. + * + * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for + * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage + * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps + * the worst of two. + */ +declare function critProbability(critRange: number, rollType?: RollType): number; +/** + * The canonical "clean miss" outcome — a point of zero damage with no rider. + * This is the {@link OutcomeType} that attribution charts and outcome stats key + * on, and is distinct from the builder's attack-resolution `miss` weight label. + */ +declare const MISS_NONE_OUTCOME: OutcomeType; +/** + * All outcome types in canonical severity order — clean miss → crit. This is + * also the natural stacking order for attribution charts (least- to + * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly + * once; use it instead of hand-maintained per-consumer outcome tables. + */ +declare const ALL_OUTCOME_TYPES: OutcomeType[]; +/** + * Outcome types in display order for stats / breakdown rows — most prominent + * first (crit, hit, …) down to the clean miss. + */ +declare const OUTCOME_DISPLAY_ORDER: OutcomeType[]; +/** + * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}). + * Labels not present in `order` sort after known ones, alphabetically — so + * ad-hoc/test labels outside the {@link OutcomeType} union stay stable. + */ +declare function sortOutcomes(outcomes: Iterable, order?: readonly string[]): T[]; +declare const onAnyHit: OutcomeType[]; +declare const onCritOnly: OutcomeType[]; +declare const onHitOnly: OutcomeType[]; +declare const onMissOnly: OutcomeType[]; +declare const onMissDamageOnly: OutcomeType[]; +declare const onSaveHalfOnly: OutcomeType[]; +declare const onSaveFailOnly: OutcomeType[]; +declare const onPotentCantripOnly: OutcomeType[]; + +/** + * Query interface for analyzing dice roll probability distributions. + * + * Combines multiple attack PMFs and provides statistical analysis methods for: + * - Basic statistics (mean, variance, min/max, percentiles) + * - Probability queries (hit chances, success rates, exact counts) + * - Damage analysis (ranges by outcome type, expected values) + * - Data export (charts, tables, visualizations) + * + */ +declare class DiceQuery { + readonly singles: PMF[]; + private readonly _eps; + private readonly _combinedProvided; + private _combined?; + private _combinedWithAttr?; + constructor(singles: PMF | PMF[], combined?: PMF, eps?: number); + /** + * The combined damage distribution of all single PMFs (their convolution), + * normalized to total probability 1. + * + * Computed lazily on first access and cached. Queries that only need + * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, + * {@link DiceQuery.stddev} — never trigger this convolution. + */ + get combined(): PMF; + private static readonly DEFAULT_OUTCOMES; + /** + * Returns a new PMF with damage attribution metadata populated. + * + * This method computes attribution on-demand for builder-generated PMFs, + * enabling them to work with damage attribution charts. The `attr` field + * tracks how much damage each outcome type contributes at each damage value. + * + * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) + * + * Performance: Cached after first call. Adds minimal overhead vs `combined`. + * + * @returns PMF with attr field populated for damage attribution charts + * + * @example + * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) + * const query = attack.toQuery() + * const pmf = query.combinedWithAttribution() + * // Now pmf can be used with toDamageAttributionChartSeries() + */ + combinedWithAttribution(): PMF; + /** + * Per-label `damage value → probability mass` series for the combined, + * attribution-carrying distribution — the provenance core of the stacked + * damage-attribution chart. Convenience for + * `combinedWithAttribution().attributionByValue()`; see + * {@link PMF.attributionByValue}. + */ + attributionByValue(): Map>; + /** + * How many of the independent single PMFs can produce the given outcome + * label. Useful for "all of them succeeded" style probabilities where the + * exponent is the number of contributing attacks (see + * {@link DiceQuery.probExactlyK}). + */ + countSinglesWith(label: string): number; + /** + * Returns the expected damage across all possible outcomes. + * + * Example: `query.mean()` → 12.5 + * Use case: "What's my average damage per round?" + */ + mean(): number; + /** + * Returns the variance of the damage distribution. + * + * Example: `query.variance()` → 45.2 + * Use case: "How much does my damage vary from the average?" + * High variance means higher risk/reward. Lower variance means more consistent damage. + */ + variance(): number; + /** + * Returns the standard deviation of the damage distribution. + * + * Example: `query.stdev()` → 6.7 + * Use case: "What's the typical spread around my average damage?" + * Used to determine how consistent the damage is. + */ + stddev(): number; + /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ + stdev(): number; + /** + * Returns the Cumulative Distribution Function. + */ + cdf(x: number): number; + /** + * Returns the probability of dealing X damage or less. + * In statistics, this is called the cumulative distribution function (CDF). + * Example: `query.cdf(20)` → 0.75 + * Use case: "What's the chance I deal 20 damage or less?" + */ + probTotalAtMost(x: number): number; + /** + * Returns the Complementary Cumulative Distribution Function. + */ + ccdf(x: number): number; + /** + * Returns the probability of dealing at least X damage. + * + * Example: `query.probTotalAtLeast(25)` → 0.35 + * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" + */ + probTotalAtLeast(threshold: number): number; + /** + * Returns damage values at specific percentiles. + * + * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] + * Use case: "What are my 25th, 50th, and 75th percentile damage values?" + */ + percentiles(percentileValues: number[]): number[]; + /** + * Returns the minimum possible damage. + * + * Example: `query.min()` → 0 + * Use case: "What's the worst-case damage if everything misses?" + */ + min(): number; + /** + * Returns the maximum possible damage. + * + * Example: `query.max()` → 56 + * Use case: "What's the best-case damage if everything crits and rolls max?" + */ + max(): number; + private singleProb; + /** + * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it + * carries ANY of `labels`", over the n independent singles. + * + * Each single's per-event success probability is the Poisson-binomial + * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), + * computed exactly once. The binomial DP then runs once to produce the whole + * distribution, so the array-label paths of probExactlyK / probAtLeastK / + * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and + * re-running the DP per requested k. + */ + private countDistribution; + probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number; + /** + * Returns the probability that at least one attack has the specified outcome(s). + * - This is the complement of probAtMostK(labels, 0) + * + * Examples: + * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) + * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) + * + * Use cases: + * - "What's the chance at least one of my attacks connects?" + * + * Note: + * + * - You have to pass in an array of labels to avoid double-counting if you are + * using multiple labels. You cannot just add them. + */ + probAtLeastOne(labels: OutcomeType | OutcomeType[]): number; + /** + * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. + * + * Uses dynamic programming to efficiently calculate the probability distribution + * of how many attacks will have the specified outcome, accounting for different + * success probabilities across individual attacks. + * + * Example: For 3 attacks with 50% hit chance each, returns: + * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] + * + * @param label - The outcome type to count + * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) + * @returns Array where index K contains P(exactly K attacks have the label) + */ + private computeBinomialProbabilities; + /** + * Returns the probability that exactly K attacks result in the specified outcome(s). + * + * Single label examples: + * - probExactlyK('hit', 2) = probability exactly 2 attacks hit + * - probExactlyK('crit', 1) = probability exactly 1 attack crits + * - probExactlyK('crit', 0) = probability no attacks crit + * + * Array examples: + * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed + * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds + * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss + * + * Use cases: + * - "What's the chance exactly one of my attacks hits?" + * - "How likely am I to get exactly 2 successes out of 3 attacks?" + * - "What's the probability that exactly half my attacks succeed?" + * + * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. + * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. + */ + probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number; + /** + * Returns the probability that AT MOST K attacks result in the specified outcome(s). + * + * Single label examples: + * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) + * - probAtMostK('crit', 0) = probability no attacks crit + * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss + * + * Array examples: + * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds + * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) + * + * Use cases: + * - "What's the chance that at most one attack hits?" (rest miss) + * - "How likely am I to have mostly failures?" (at most 1 success) + * - "What's the probability of a really bad turn?" (at most 0 successes) + * + */ + probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number; + /** + * Returns the expected damage attributed to specific outcome types. + * + * Single label examples: + * - expectedDamageFrom('hit') = expected damage from hit components + * - expectedDamageFrom('crit') = expected damage from crit components + * + * Array examples: + * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success + * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses + * + * Use cases: + * - "How much damage do I expect from successful attacks?" + * - "What's the damage contribution from critical hits specifically?" + * - "How much damage comes from miss effects (like save-for-half spells)?" + */ + expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number; + /** + * Returns damage statistics for scenarios where AT LEAST ONE attack results in + * the specified outcome(s). + * + * This method answers "What happens when things go reasonably well?" rather than + * "What's the theoretical maximum?" It includes mixed scenarios which are more + * common and tactically relevant than pure scenarios. + * + * Single label examples: + * - damageStatsFrom('hit') = damage range when at least one attack hits + * - damageStatsFrom('crit') = damage range when at least one attack crits + * + * Array examples: + * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds + * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses + * + * Tactical Use Cases: + * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" + * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" + * - "Should I use this risky spell if it has good damage when it works?" + * - "What's my damage potential when something goes right?" (vs pure failure) + * + * Combat Planning Examples: + * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" + * (Much more useful than "You average 50 damage including complete misses") + * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" + * - Resource management: "If I hit anything, I'll likely finish this enemy" + * + * Statistical Note: + * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which + * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. + * + * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an + * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and + * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than + * E[dmg | the label occurs]. For a single attack both are the plain + * conditional figures. Use {@link probAtLeastOne} for the scenario probability. + * + * @example + * // High-level tactical planning + * const successStats = query.damageStatsFrom('hit') + * const successChance = query.probAtLeastOne('hit') + * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) + */ + damageStatsFrom(labels: OutcomeType | OutcomeType[]): { + min: number; + max: number; + avg: number; + count: number; + }; + /** + * Returns damage statistics for scenarios where ALL attacks result in the specified + * outcome, calculated by leveraging the pure partition of singles. + * + * This method answers "What's the theoretical best/worst case?" and "What are the + * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. + * + * Examples: + * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) + * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) + * + * UI and Display Use Cases: + * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) + * - "Best case scenario" vs "worst case scenario" analysis + * - Mathematical verification: "Does our hit damage calculation match manual math?" + * - Clean damage type attribution: "How much comes from base hits vs crits?" + * + * Design and Balance Use Cases: + * - Game designers: "What's the damage ceiling if someone gets lucky?" + * - Character optimization: "What's my absolute maximum potential?" + * - Ability comparison: "Which build has higher crit ceiling?" + * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" + * + * Mathematical Use Cases: + * - Validating complex calculations against simple manual math + * - Understanding damage component contributions in isolation + * - Separating luck (crit variance) from consistency (hit variance) + * - Building intuition about damage sources + * + * When to Use This vs damageStatsFrom(): + * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons + * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios + * + * Statistical Note: + * Pure scenarios (all hits, all crits) are rare but represent clear mathematical + * boundaries. These stats help understand the "shape" of your damage potential. + * + * @example + * // UI display logic + * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" + * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" + * + * // vs tactical planning (use damageStatsFrom instead) + * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios + */ + combinedDamageStats(targetLabel: OutcomeType): { + min: number; + max: number; + avg: number; + count: number; + }; + /** + * Returns the probability that at least one attack carries ANY of the + * specified labels (the marginal P(≥1) across the independent attacks). + * + * Examples: + * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) + * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) + * + * Use cases: + * - "What's the chance my resolution includes a success label?" + * - "How likely am I to get any hits or crits across all attacks?" + * + * Note: this must NOT be computed by summing `combined` bin probabilities. A + * single combined damage total is reachable by many outcome combinations and + * a bin can hold several labels at once, so summing `bin.p` over bins that + * contain a label over-counts. The correct marginal is the Poisson-binomial + * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. + */ + probabilityOf(labels: OutcomeType | OutcomeType[]): number; + /** + * Returns the probability of missing (any type of miss). + * + * Example: `query.missChance()` → 0.04 + * Use case: "What's the chance I miss completely this turn?" + */ + missChance(): number; + /** + * Returns data formatted for plotting damage probability distribution. + * + * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] + * Use case: "I want to visualize my damage distribution in a chart." + */ + toChartSeries(): Array<{ + x: number; + y: number; + }>; + /** + * Returns tabular data showing damage values and their probability breakdowns. + * + * Example: `query.toLabeledTable(['hit', 'crit'])` → + * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] + * + * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." + */ + toLabeledTable(labels?: OutcomeType[]): Array<{ + damage: number; + total: number; + } & Record>; + /** + * Returns data for stacked charts with unconditional per-label probability mass per damage. + * + * - Each dataset value equals the unconditional probability mass for that label at that damage + * (i.e., `bin.count[label]`). + * - Column sums may be less than the total probability `bin.p` when you omit labels or when + * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. + * - This behavior matches tests that expect raw per-label mass (not proportional scaling). + * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. + * + * @example + * query.toStackedChartData(['hit', 'crit']) + * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} + */ + toStackedChartData(labels?: OutcomeType[], epsilon?: number): { + labels: number[]; + datasets: Array<{ + label: string; + data: number[]; + }>; + }; + /** + * Returns pure mathematical data for attribution charts showing outcome contributions. + * + * Automatically discovers all outcome types present in the PMF, applies filtering rules, + * and returns proportional data suitable for stacked visualization. + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and proportional data + * + * @example + * query.toAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} + */ + toAttributionChartSeries(options?: { + stackOrder?: string[]; + filterRules?: (outcome: string, damage: number) => boolean; + asPercentages?: boolean; + }): { + support: number[]; + outcomes: string[]; + data: { + [outcome: string]: number[]; + }; + }; + /** + * Returns pure mathematical data for damage attribution charts showing damage contribution + * from each outcome type at each damage value. + * + * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of + * bin.count (probability attribution). + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) + * @returns Pure data structure with support, outcomes, and damage attribution data + * + * @example + * query.toDamageAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} + */ + toDamageAttributionChartSeries(options?: { + stackOrder?: string[]; + filterRules?: (outcome: string, damage: number) => boolean; + asPercentages?: boolean; + }): { + support: number[]; + outcomes: string[]; + data: { + [outcome: string]: number[]; + }; + }; + /** + * Returns pure mathematical data for outcome attribution charts showing which + * attack outcome combinations can produce each damage value. + * + * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks + * outcome combinations - answering "what attack outcomes produced this damage?" + * + * @param options Configuration options + * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) + * @param options.filterRules Function to determine if outcome should be included for a given damage value + * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support, outcomes, and outcome combination probabilities + * + * @example + * query.toOutcomeAttributionChartSeries() + * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} + */ + toOutcomeAttributionChartSeries(options?: { + stackOrder?: string[]; + filterRules?: (outcome: string, damage: number) => boolean; + asPercentages?: boolean; + }): { + support: number[]; + outcomes: string[]; + data: { + [outcome: string]: number[]; + }; + }; + /** + * Returns pure mathematical data for cumulative distribution function (CDF). + * Shows P(X ≤ x) - the probability of getting at most x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and cumulative probabilities + * + * @example + * query.toCDFSeries() + * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} + */ + toCDFSeries(asPercentages?: boolean): { + support: number[]; + data: number[]; + }; + /** + * Returns pure mathematical data for complementary cumulative distribution function (CCDF). + * Shows P(X ≥ x) - the probability of getting at least x damage. + * + * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) + * @returns Pure data structure with support and complementary cumulative probabilities + * + * @example + * query.toCCDFSeries() + * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} + */ + toCCDFSeries(asPercentages?: boolean): { + support: number[]; + data: number[]; + }; + /** Probability of doing strictly more than threshold damage (default >0). */ + probDamageGreaterThan(threshold?: number): number; + /** All outcome keys actually present (typed & ordered if you pass an order). */ + outcomeKeys(order?: OutcomeType[]): OutcomeType[]; + /** Total probability per outcome across the PMF. */ + outcomeTotals(outcomes?: OutcomeType[]): Map; + /** Conditional damage range per outcome (min/avg/max of X | outcome). */ + outcomeDamageRanges(outcomes?: OutcomeType[]): Map; + /** + * Snapshot of the distribution in the exact shape the UI consumes. + * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) + * - damageRange is conditional on the outcome occurring + * + * The outcome probabilities use the correct Poisson-binomial marginals + * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), + * so they are always valid probabilities in [0,1]. + * + * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from + * the combined PMF's `count`, which the convolution accumulates as an EXPECTED + * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] + * rather than a clean conditional expectation. It is correct for a single + * attack. + */ + snapshot(order?: readonly OutcomeType[]): Snapshot; + /** + * PMF Transformation Methods + * + * These methods provide a fluent API for transforming dice queries by wrapping + * the underlying PMF transformation methods. All operations work on the combined + * PMF and return new DiceQuery instances. + */ + /** + * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). + * + * @returns New DiceQuery with normalized combined PMF + */ + normalize(): DiceQuery; + /** + * Returns a new DiceQuery with low-probability outcomes removed. + * + * @param eps Minimum probability threshold (defaults to PMF epsilon) + * @param keepFinalBin Whether to keep the highest damage bin regardless of probability + * @returns New DiceQuery with compacted combined PMF + */ + compact(eps?: number, keepFinalBin?: boolean): DiceQuery; + /** + * Returns a new DiceQuery with an additional scaled branch added. + * Useful for conditional outcomes like "30% chance of opportunity attack". + * + * @param branch DiceQuery to add as a scaled branch + * @param probability Probability of the branch occurring (0-1) + * @returns New DiceQuery combining this query with the scaled branch + * + * @example + * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); + * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); + */ + addScaled(branch: DiceQuery, probability: number): DiceQuery; + /** + * Returns a new DiceQuery with all probabilities scaled by a factor. + * Used for conditional scenarios where the entire outcome has reduced probability. + * + * @param factor Scaling factor for probabilities + * @returns New DiceQuery with scaled probabilities + * + * @example + * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario + */ + scaleMass(factor: number): DiceQuery; + totalMass(): number; + /** + * Returns a new DiceQuery with damage values transformed by a function. + * Useful for applying modifiers, resistances, or other damage transformations. + * + * @param damageTransformFunction Function to transform each damage value + * @returns New DiceQuery with transformed damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage + * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage + */ + mapDamage(damageTransformFunction: (damageValue: number) => number): DiceQuery; + /** + * Returns a new DiceQuery with damage values scaled by a factor. + * Convenient wrapper around mapDamage for multiplicative scaling. + * + * @param factor Scaling factor for damage values + * @param rounding Rounding method: "floor" (default), "round", or "ceil" + * @returns New DiceQuery with scaled damage values + * + * @example + * const baseAttack = parse("2d6 + 3"); + * const doubled = baseAttack.scaleDamage(2); // Double damage + * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded + */ + scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): DiceQuery; + /** + * Returns a new DiceQuery combining this query with another via convolution. + * Equivalent to rolling both queries independently and adding results. + * It is important to use this rather than combing()ing the PMFs directly! + * This method maintains the provenance of the PMFs which is needed for damage attribution. + * Combining the .combined PMFs directly is still valid for DPR calculations but + * is not statistically sound for queries. + * + * @param other DiceQuery to combine with + * @param eps Optional epsilon for precision control + * @returns New DiceQuery representing the combined outcome + * + * @example + * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); + * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); + * const bothAttacks = mainAttack.convolve(bonusAttack); + */ + convolve(other: DiceQuery): DiceQuery; + /** + * First-success split over an ordered list of DISTINCT single-swing PMFs. + * Each PMF may have different success/subset probabilities (from labels). + * + * successOutcome: e.g., ["success"] or ["hit", "crit"] + * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success + * + * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] + */ + firstSuccessSplit(successOutcome: OutcomeType | OutcomeType[], subsetOutcome: OutcomeType | OutcomeType[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number]; +} +type OutcomeSnapshot = { + atLeastOneProbability: number; + allProbability: number; + damageRange: { + min: number; + avg: number; + max: number; + }; +}; +type Snapshot = { + averageDPR: number; + damageChance: number; + percentiles: { + p25: number; + p50: number; + p75: number; + }; + outcomes: Map; +}; + +declare const pmfCache: LRUCache; +/** + * Probability Mass Function for discrete damage distributions. + */ +declare class PMF { + readonly map: Map; + readonly epsilon: number; + readonly normalized: boolean; + readonly identifier: string; + private _preservedProvenance; + private static __anonIdCounter; + private _support?; + private _min?; + private _max?; + private _totalMass?; + private _mean?; + private _variance?; + private _stdev?; + private _fingerprint?; + constructor(map?: Map, epsilon?: number, normalized?: boolean, identifier?: string, _preservedProvenance?: boolean); + static empty(epsilon?: number, identifier?: string): PMF; + static zero(epsilon?: number): PMF; + static delta(value: number, epsilon?: number): PMF; + /** + * Point mass at damage 0 tagged with the canonical `missNone` outcome. + * + * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the + * builder's attack-resolution vocabulary. This uses the `missNone` + * {@link OutcomeType} that the attribution charts and outcome stats key on, + * so it is the correct "clean miss / no damage" delta for provenance-aware + * mixtures feeding those consumers. + */ + static missNone(epsilon?: number): PMF; + static emptyMass(): PMF; + [Symbol.iterator](): IterableIterator<[number, Bin]>; + static clearCache(): void; + /** + * Creates a conditional PMF from two branches (success and failure) and a probability. + * This is the core logic for modeling any probabilistic event where there are two + * distinct outcomes. + */ + static branch(successPMF: PMF, failurePMF: PMF, successProbability: number): PMF; + /** + * withProbability() + * + * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). + * + * Think of this as a shortcut for: + * pmf.gate(p, PMF.zero()) + * + * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, + * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. + * + * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. + * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. + * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) + * use query.firstSuccessSplit() to get the exact probabilities. + * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). + * + */ + static withProbability(successPMF: PMF, probability: number): PMF; + /** + * gate() + * + * A conditional wrapper around branch() that applies this PMF with probability `p`, + * and applies a provided fallback PMF otherwise. + * + * This is useful for modeling a binary choice between two outcomes: + * - The "success" outcome (this PMF) happens with probability `p`. + * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. + * + * Examples: + * - 25% chance to include an opportunity attack, otherwise nothing: + * attackPMF.gate(0.25, PMF.zero()) + * + * - 50% chance to deal fireball damage, otherwise cone of cold damage: + * fireballPMF.gate(0.5, coneOfColdPMF) + * + * Relationship to other helpers: + * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. + * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). + * + * @param p Probability of applying this PMF (between 0 and 1). + * @param fallback PMF to apply when this PMF is *not* selected. + * @returns A new PMF representing the weighted mixture of this PMF and the fallback. + */ + gate(p: number, fallback: PMF): PMF; + /** + * PMF.exclusive() + * + * Builds a single PMF from a set of mutually exclusive weighted outcomes. + * Exactly one of the provided options will occur. + * + * Each option has: + * - A PMF representing its outcome (e.g., damage dice). + * - A weight representing its probability of being selected. + * + * Notes: + * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for floating point rounding. + */ + static exclusive(options: Array<{ + pmf: PMF; + weight: number; + } | [PMF, number]>, eps?: number): PMF; + /** + * PMF.mix() + * + * Builds a PMF as a linear combination of input PMFs with the given weights. + * Unlike `exclusive`, this does NOT: + * - enforce that weights sum to 1 + * - add leftover probability to δ0 (PMF.zero()) + * + * Use when outcomes are not mutually exclusive, or for interpolation/blending. + * + * @param options Array of `{ pmf, weight }` or `[PMF, number]`. + * @param eps Optional tolerance for skipping tiny weights. + */ + static mix(options: Array<{ + pmf: PMF; + weight: number; + } | [PMF, number]>, eps?: number): PMF; + /** + * Adds damage attribution metadata to this PMF based on existing count metadata. + * For each bin, sets attr[outcome] = damage × count[outcome]. + * + * This enables damage attribution charts to work with builder-generated PMFs. + * The parser generates attr automatically, but builder PMFs only have count. + * + * @returns New PMF with attr field populated in each bin + */ + /** + * Returns true if this PMF already carries damage attribution metadata. + * + * Only the first positive-damage bin is inspected (parser-generated PMFs + * populate `attr` uniformly), so this is O(1) in practice. + */ + hasAttribution(): boolean; + withAttribution(): PMF; + /** + * General-purpose N-way mixture. + * weights: Array of [weight, PMF]. + * + * Example: PMF.mixN([ + * [pMiss, zero], + * [pHit, hitPMF], + * [pCrit, critPMF], + * ]); + */ + static mixN(weights: [number, PMF][], eps?: number): PMF; + private setPreservedProvenance; + preservedProvenance(): boolean; + private getPowerCacheKey; + /** + * Efficiently computes this PMF convolved with itself `n` times. + * Uses exponentiation by squaring to reduce total convolutions. + * n must be a positive integer. + * * + * * NOTE: This folds multiple independent attacks into a single PMF. + * As a result, The power() method causes a loss of data provenance. + * This is ONLY SAFE if you are trying to calculate masses. + * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). + */ + power(n: number, eps?: number): PMF; + replicate(n: number): PMF[]; + mass(): number; + outcomeMass(outcome: string): number; + faceTotal(): number; + normalize(): PMF; + /** + * Returns a copy with negligible probabilities removed (p < eps). + * If keepFinalBin is true, the bin with the largest key is always kept, + * even if its probability is below eps. count/attr submaps are still cleaned. + */ + compact(eps?: number, keepFinalBin?: boolean): PMF; + support(): number[]; + min(): number; + max(): number; + /** + * Returns the expected (mean) damage value. + * Cached for performance since this requires iterating through all bins. + */ + mean(): number; + /** + * Returns the variance of the damage distribution. + * Cached for performance since this requires mean calculation plus iteration. + */ + variance(): number; + /** + * Returns the standard deviation of the damage distribution. + */ + stdev(): number; + /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ + private static cloneBin; + /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ + private static scaleBin; + private static mergeInto; + add(other: PMF): PMF; + /** + * Returns a new PMF with a scaled branch added to this one. + * The branch PMF is scaled by the given probability before merging + * This will be very useful for conditional effects and for being + * able to model "I can probably have this opportunity attack 40% of rounds" + * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes + */ + addScaled(branch: PMF, probability: number): PMF; + /** + * Redistributes probability mass to model an effect that only occurs with + * probability `frequency` — a conditional attack, an on-hit rider, or a + * sub-one AoE target fraction. + * + * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, + * per-label `count`, AND per-label `attr` — and the freed mass is moved into + * the miss bin at damage 0, tagged with the canonical `missNone` outcome. + * Total probability mass is preserved. + * + * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage + * attribution (`attr`) intact, so a frequency-scaled PMF still renders + * correctly in the damage-attribution charts. + * + * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` + * collapses all mass into the miss bin. The miss outcome is assumed to be + * encoded at damage value 0. + * + * @param frequency Probability in [0, 1] that the effect occurs. + */ + applyHitFrequency(frequency: number): PMF; + scaleMass(factor: number): PMF; + mapDamage(damageTransformFunction: (damageValue: number) => number): PMF; + scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): PMF; + private getPMFCombineCacheKey; + /** + * A small content fingerprint (mass + bin count + face sum) so convolution + * cache keys change if the underlying numbers do. Memoized because a PMF is + * immutable once constructed — this avoids re-summing every key on each + * convolve() call (including cache hits). + */ + fingerprint(): string; + convolve(other: PMF, eps?: number, raw?: boolean): PMF; + combineRaw(other: PMF, eps?: number): PMF; + private static reduceConvolveLeft; + /** + * Convolves multiple PMFs using linear convolution with automatic caching. + * Uses a left-to-right accumulation approach for maximum cache reuse. + * Each convolve() call automatically uses the convolution cache for performance. + * + * This linear approach provides better cache hits than pairwise because: + * - Intermediate results are more predictable and stable + * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) + * - Order-independent cache keys work better with consistent build patterns + */ + static convolveMany(pmfList: PMF[], eps?: number): PMF; + /** + * Returns a plain, JSON-serializable representation of this PMF. + * + * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces + * the expected output (no double-encoding). Use {@link PMF.fromJSON} to + * reconstruct, or {@link PMF.toJSONString} if you need the string directly. + */ + toJSON(): { + bins: Array<[number, Bin]>; + normalized: boolean; + identifier: string; + }; + /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ + toJSONString(): string; + static fromJSON(jsonData: { + bins: Array<[number, Bin]>; + normalized?: boolean; + identifier?: string; + }): PMF; + /** + * Relative pruning with optional top-K floor. + * Keeps bins with p >= epsRel * peak, always keeps min and max damage, + * optionally guarantees at least `minBins` survivors by adding top-K. + * Returns a new, non-normalized PMF. + */ + prune(epsRel: number, minBins?: number): PMF; + /** Probability mass at exactly x. */ + pAt(x: number): number; + /** + * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. + * Assumes a miss is encoded as the damage-0 bin (the convention used across + * attack/save PMFs). The dual of {@link missProbability}. + */ + hitProbability(): number; + /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ + missProbability(): number; + /** + * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width + * damage buckets, aggregating probability mass (and `count`/`attr` + * provenance) into each bucket's start value. Returns this PMF unchanged when + * its integer support already fits within `maxBuckets`. + * + * This is a lossy display/downsampling transform (bucket start replaces the + * exact damage value) — use it for charting wide distributions, not for DPR + * math. + */ + rebin(maxBuckets: number): PMF; + /** Dense integer support from min..max (inclusive). + * Useful for showing empty bars in charts. + */ + denseSupport(): number[]; + /** CDF at x: P(X ≤ x). */ + cdfAt(x: number): number; + /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ + quantile(p: number): number; + /** Get outcome probability at specific damage value. */ + outcomeAt(damage: number, outcome: string): number; + /** Get all outcome types present in this PMF. */ + outcomes(): string[]; + /** Get total probability of an outcome across all damage values. */ + outcomeProbability(outcome: string): number; + /** Get damage attribution for an outcome at specific damage value. */ + outcomeAttributionAt(damage: number, outcome: string): number; + /** Get all outcome data at specific damage value. */ + binAt(damage: number): { + p: number; + count: Record; + attr?: Record; + } | null; + /** Check if outcome exists in this PMF. */ + hasOutcome(outcome: string): boolean; + /** + * Split each damage value's probability mass across outcome labels, returning + * per-label maps of `damage value → probability mass attributable to that + * label`. Summing over labels at a given value recovers that value's `p`. + * + * Damage-bearing bins are split by `attr` weight (the share of damage each + * outcome contributed); the clean-miss bin at 0 is split by `count` weight + * (there is no damage to attribute). Attribution is computed on demand via + * {@link withAttribution} when absent, so builder-generated PMFs work too. + * + * This is the provenance core of the stacked damage-attribution chart — the + * caller only maps these series into its rendering format (colors, binning, + * axis labels). + */ + attributionByValue(): Map>; + tailProbGE(t: number): number; + tailProbGT(t: number): number; + /** + * Returns a new PMF containing only bins where the specified outcome has non-zero probability. + * This creates a marginal distribution for the given outcome type, with probabilities + * scaled to represent the unconditional mass attributable to that outcome. + */ + filterOutcome(outcome: string): PMF; + /** + * Calculates probabilities for first-success outcomes across n independent attempts. + * + * @param pSuccess - Total probability of any success on a single attempt. + * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). + * @param n - Number of independent attempts. + * + * Returns: + * - pSpecificSuccess: Probability that the first success was of the "special" type + * - pGeneralSuccess: Probability that the first success was of the non-special type + * - pNone: Probability that no successes occurred + * - pAny: Probability that at least one success occurred + */ + static firstSuccessWeights(pSuccess: number, pSpecial: number, n: number): { + pSpecificSuccess: number; + pGeneralSuccess: number; + pNone: number; + pAny: number; + }; + mapValues(f: (v: number) => number, eps?: number, opts?: { + rounding?: Rounding; + preserveCounts?: boolean; + }): PMF; + static fromMap(m: Map, eps?: number, { requireIntegerValues }?: { + requireIntegerValues?: boolean; + }): PMF; + query(): DiceQuery; +} + +export { ALL_OUTCOME_TYPES as A, type Bin as B, type CritConfig as C, type DamageDistribution as D, EPS as E, LRUCache as L, MISS_NONE_OUTCOME as M, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type OutcomeType as a, type RollType as b, critProbability as c, OUTCOME_DISPLAY_ORDER as d, onCritOnly as e, onHitOnly as f, onMissOnly as g, onMissDamageOnly as h, onSaveHalfOnly as i, onSaveFailOnly as j, onPotentCantripOnly as k, DiceQuery as l, type OutcomeSnapshot as m, onAnyHit as o, pmfCache as p, sortOutcomes as s }; From f653350a108dff5e9ab568488662d7cb7a6145ad Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 07:14:12 +0000 Subject: [PATCH 3/4] chore: stop vendoring built dist (publish to npm instead) The dist/ was committed only as a bridge so a consumer could install this branch as a git dependency before 0.4.0 was published. It's build output (regenerated by prepublishOnly/prepare), gitignored, and bloated the diff by ~21.5k lines. Removing it from tracking leaves the branch as the ~258-line source change. Publish 0.4.0 to npm; consumers then depend on ^0.4.0 rather than a git ref. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX --- dist/builder/index.cjs | 5501 ------------------------------------ dist/builder/index.cjs.map | 1 - dist/builder/index.d.cts | 429 --- dist/builder/index.d.ts | 429 --- dist/builder/index.js | 5474 ----------------------------------- dist/builder/index.js.map | 1 - dist/index.cjs | 3627 ------------------------ dist/index.cjs.map | 1 - dist/index.d.cts | 111 - dist/index.d.ts | 111 - dist/index.js | 3601 ----------------------- dist/index.js.map | 1 - dist/pmf-D5VRghZI.d.cts | 1129 -------- dist/pmf-D5VRghZI.d.ts | 1129 -------- 14 files changed, 21545 deletions(-) delete mode 100644 dist/builder/index.cjs delete mode 100644 dist/builder/index.cjs.map delete mode 100644 dist/builder/index.d.cts delete mode 100644 dist/builder/index.d.ts delete mode 100644 dist/builder/index.js delete mode 100644 dist/builder/index.js.map delete mode 100644 dist/index.cjs delete mode 100644 dist/index.cjs.map delete mode 100644 dist/index.d.cts delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.js delete mode 100644 dist/index.js.map delete mode 100644 dist/pmf-D5VRghZI.d.cts delete mode 100644 dist/pmf-D5VRghZI.d.ts diff --git a/dist/builder/index.cjs b/dist/builder/index.cjs deleted file mode 100644 index bd3d208..0000000 --- a/dist/builder/index.cjs +++ /dev/null @@ -1,5501 +0,0 @@ -'use strict'; - -// src/common/lru-cache.ts -var LRUCache = class { - constructor(maxSize = 1e3) { - this.maxSize = maxSize; - this.cache = /* @__PURE__ */ new Map(); - } - get(key) { - const value = this.cache.get(key); - if (value === void 0) return void 0; - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - delete(key) { - this.cache.delete(key); - } - set(key, value) { - if (this.cache.size >= this.maxSize && !this.cache.has(key)) { - const oldestKey = this.cache.keys().next().value; - this.cache.delete(oldestKey); - } - this.cache.delete(key); - this.cache.set(key, value); - return this; - } - clear() { - this.cache.clear(); - } - get size() { - return this.cache.size; - } - has(key) { - return this.cache.has(key); - } - keys() { - return this.cache.keys(); - } - values() { - return this.cache.values(); - } -}; - -// src/common/types.ts -var EPS = 1e-12; -var MISS_NONE_OUTCOME = "missNone"; - -// src/pmf/query.ts -var _DiceQuery = class _DiceQuery { - constructor(singles, combined, eps = EPS) { - this.singles = Array.isArray(singles) ? singles : [singles]; - if (this.singles.some((s) => s === void 0)) { - throw new Error("DiceQuery contains undefined singles"); - } - this._eps = eps; - this._combinedProvided = combined !== void 0; - if (combined !== void 0) { - this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); - } - } - /** - * The combined damage distribution of all single PMFs (their convolution), - * normalized to total probability 1. - * - * Computed lazily on first access and cached. Queries that only need - * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, - * {@link DiceQuery.stddev} — never trigger this convolution. - */ - get combined() { - if (this._combined === void 0) { - const c = PMF.convolveMany(this.singles); - this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); - } - return this._combined; - } - /** - * Returns a new PMF with damage attribution metadata populated. - * - * This method computes attribution on-demand for builder-generated PMFs, - * enabling them to work with damage attribution charts. The `attr` field - * tracks how much damage each outcome type contributes at each damage value. - * - * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) - * - * Performance: Cached after first call. Adds minimal overhead vs `combined`. - * - * @returns PMF with attr field populated for damage attribution charts - * - * @example - * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) - * const query = attack.toQuery() - * const pmf = query.combinedWithAttribution() - * // Now pmf can be used with toDamageAttributionChartSeries() - */ - combinedWithAttribution() { - if (this._combinedWithAttr) { - return this._combinedWithAttr; - } - if (this.singles.every((pmf) => pmf.hasAttribution())) { - this._combinedWithAttr = this.combined; - return this._combinedWithAttr; - } - const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); - const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); - const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); - this._combinedWithAttr = normalized; - return normalized; - } - /** - * Per-label `damage value → probability mass` series for the combined, - * attribution-carrying distribution — the provenance core of the stacked - * damage-attribution chart. Convenience for - * `combinedWithAttribution().attributionByValue()`; see - * {@link PMF.attributionByValue}. - */ - attributionByValue() { - return this.combinedWithAttribution().attributionByValue(); - } - /** - * How many of the independent single PMFs can produce the given outcome - * label. Useful for "all of them succeeded" style probabilities where the - * exponent is the number of contributing attacks (see - * {@link DiceQuery.probExactlyK}). - */ - countSinglesWith(label) { - let count = 0; - for (const single of this.singles) { - if (single.hasOutcome(label)) count++; - } - return count; - } - /** - * Returns the expected damage across all possible outcomes. - * - * Example: `query.mean()` → 12.5 - * Use case: "What's my average damage per round?" - */ - mean() { - if (this._combinedProvided) { - let m = 0; - for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; - return m; - } - let totalMean = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; - } - return totalMean; - } - /** - * Returns the variance of the damage distribution. - * - * Example: `query.variance()` → 45.2 - * Use case: "How much does my damage vary from the average?" - * High variance means higher risk/reward. Lower variance means more consistent damage. - */ - variance() { - if (this._combinedProvided) { - const mu = this.mean(); - let v = 0; - for (const [damageValue, bin] of this.combined) { - const dev = damageValue - mu; - v += dev * dev * bin.p; - } - return v; - } - let totalVariance = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - if (Math.abs(mass - 1) <= this._eps) { - totalVariance += single.variance(); - } else { - let mu = 0; - for (const [d2, b] of single) mu += d2 * (b.p / mass); - let v = 0; - for (const [d2, b] of single) { - const dev = d2 - mu; - v += dev * dev * (b.p / mass); - } - totalVariance += v; - } - } - return totalVariance; - } - /** - * Returns the standard deviation of the damage distribution. - * - * Example: `query.stdev()` → 6.7 - * Use case: "What's the typical spread around my average damage?" - * Used to determine how consistent the damage is. - */ - stddev() { - return Math.sqrt(this.variance()); - } - /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ - stdev() { - return this.stddev(); - } - /** - * Returns the Cumulative Distribution Function. - */ - cdf(x) { - return this.probTotalAtMost(x); - } - /** - * Returns the probability of dealing X damage or less. - * In statistics, this is called the cumulative distribution function (CDF). - * Example: `query.cdf(20)` → 0.75 - * Use case: "What's the chance I deal 20 damage or less?" - */ - probTotalAtMost(x) { - let cumulativeProbability = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue <= x) { - cumulativeProbability += probabilityBin.p; - } - } - return cumulativeProbability; - } - /** - * Returns the Complementary Cumulative Distribution Function. - */ - ccdf(x) { - return this.probTotalAtLeast(x); - } - /** - * Returns the probability of dealing at least X damage. - * - * Example: `query.probTotalAtLeast(25)` → 0.35 - * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" - */ - probTotalAtLeast(threshold) { - let probabilitySum = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue >= threshold) { - probabilitySum += probabilityBin.p; - } - } - return probabilitySum; - } - /** - * Returns damage values at specific percentiles. - * - * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] - * Use case: "What are my 25th, 50th, and 75th percentile damage values?" - */ - percentiles(percentileValues) { - const sortedDamageValues = this.combined.support(); - if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); - const cumulativeProbabilities = []; - let runningProbabilitySum = 0; - for (const damageValue of sortedDamageValues) { - runningProbabilitySum += this.combined.map.get(damageValue).p; - cumulativeProbabilities.push(runningProbabilitySum); - } - return percentileValues.map((targetPercentile) => { - let leftBound = 0; - let rightBound = cumulativeProbabilities.length - 1; - while (leftBound <= rightBound) { - const middleIndex = Math.floor((leftBound + rightBound) / 2); - if (cumulativeProbabilities[middleIndex] >= targetPercentile) { - rightBound = middleIndex - 1; - } else { - leftBound = middleIndex + 1; - } - } - return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; - }); - } - /** - * Returns the minimum possible damage. - * - * Example: `query.min()` → 0 - * Use case: "What's the worst-case damage if everything misses?" - */ - min() { - return this.combined.min(); - } - /** - * Returns the maximum possible damage. - * - * Example: `query.max()` → 56 - * Use case: "What's the best-case damage if everything crits and rolls max?" - */ - max() { - return this.combined.max(); - } - singleProb(diceIndex, label) { - const single = this.singles[diceIndex]; - let probabilitySum = 0; - for (const [, probabilityBin] of single) { - probabilitySum += probabilityBin.count[label] || 0; - } - const mass = single.mass(); - return mass > 0 ? probabilitySum / mass : 0; - } - /** - * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it - * carries ANY of `labels`", over the n independent singles. - * - * Each single's per-event success probability is the Poisson-binomial - * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), - * computed exactly once. The binomial DP then runs once to produce the whole - * distribution, so the array-label paths of probExactlyK / probAtLeastK / - * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and - * re-running the DP per requested k. - */ - countDistribution(labels) { - const n = this.singles.length; - const successProbabilities = this.singles.map( - (single) => new _DiceQuery([single]).probabilityOf(labels) - ); - const dist = new Array(n + 1).fill(0); - dist[0] = 1; - for (const successProb of successProbabilities) { - for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { - dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; - } - dist[0] *= 1 - successProb; - } - return dist; - } - probAtLeastK(labels, k) { - const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; - const n = this.singles.length; - if (k <= 0) return 1; - if (k > n) return 0; - const dist = this.countDistribution(L); - let tail = 0; - for (let i = k; i <= n; i++) { - tail += dist[i]; - } - if (tail < 0) return 0; - if (tail > 1) return 1; - return tail; - } - /** - * Returns the probability that at least one attack has the specified outcome(s). - * - This is the complement of probAtMostK(labels, 0) - * - * Examples: - * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) - * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) - * - * Use cases: - * - "What's the chance at least one of my attacks connects?" - * - * Note: - * - * - You have to pass in an array of labels to avoid double-counting if you are - * using multiple labels. You cannot just add them. - */ - probAtLeastOne(labels) { - if (typeof labels === "string") { - labels = [labels]; - } - let productOfNonOccurrence = 1; - for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { - let combinedProbability = 0; - for (const label of labels) { - combinedProbability += this.singleProb(diceIndex, label); - } - if (combinedProbability < 0) combinedProbability = 0; - else if (combinedProbability > 1) combinedProbability = 1; - productOfNonOccurrence *= 1 - combinedProbability; - } - const result = 1 - productOfNonOccurrence; - return result < 0 ? 0 : result > 1 ? 1 : result; - } - /** - * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. - * - * Uses dynamic programming to efficiently calculate the probability distribution - * of how many attacks will have the specified outcome, accounting for different - * success probabilities across individual attacks. - * - * Example: For 3 attacks with 50% hit chance each, returns: - * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] - * - * @param label - The outcome type to count - * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) - * @returns Array where index K contains P(exactly K attacks have the label) - */ - computeBinomialProbabilities(label, maxK) { - const individualProbabilities = this.singles.map( - (_, diceIndex) => this.singleProb(diceIndex, label) - ); - const binomialProbs = new Array(maxK + 1).fill(0); - binomialProbs[0] = 1; - for (const singleProbability of individualProbabilities) { - for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { - binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; - } - binomialProbs[0] *= 1 - singleProbability; - } - return binomialProbs; - } - /** - * Returns the probability that exactly K attacks result in the specified outcome(s). - * - * Single label examples: - * - probExactlyK('hit', 2) = probability exactly 2 attacks hit - * - probExactlyK('crit', 1) = probability exactly 1 attack crits - * - probExactlyK('crit', 0) = probability no attacks crit - * - * Array examples: - * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed - * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds - * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss - * - * Use cases: - * - "What's the chance exactly one of my attacks hits?" - * - "How likely am I to get exactly 2 successes out of 3 attacks?" - * - "What's the probability that exactly half my attacks succeed?" - * - * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. - * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. - */ - probExactlyK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - return probabilityArray[k]; - } - const dist = this.countDistribution(labels); - return k >= 0 && k < dist.length ? dist[k] : 0; - } - /** - * Returns the probability that AT MOST K attacks result in the specified outcome(s). - * - * Single label examples: - * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) - * - probAtMostK('crit', 0) = probability no attacks crit - * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss - * - * Array examples: - * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds - * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) - * - * Use cases: - * - "What's the chance that at most one attack hits?" (rest miss) - * - "How likely am I to have mostly failures?" (at most 1 success) - * - "What's the probability of a really bad turn?" (at most 0 successes) - * - */ - probAtMostK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - let cumulativeSum2 = 0; - for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { - cumulativeSum2 += probabilityArray[outcomeCount]; - } - return cumulativeSum2; - } - const dist = this.countDistribution(labels); - const upper = Math.min(k, dist.length - 1); - let cumulativeSum = 0; - for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { - cumulativeSum += dist[outcomeCount]; - } - return cumulativeSum; - } - /** - * Returns the expected damage attributed to specific outcome types. - * - * Single label examples: - * - expectedDamageFrom('hit') = expected damage from hit components - * - expectedDamageFrom('crit') = expected damage from crit components - * - * Array examples: - * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success - * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses - * - * Use cases: - * - "How much damage do I expect from successful attacks?" - * - "What's the damage contribution from critical hits specifically?" - * - "How much damage comes from miss effects (like save-for-half spells)?" - */ - expectedDamageFrom(labels) { - const wanted = Array.isArray(labels) ? labels : [labels]; - let total = 0; - for (const single of this.singles) { - for (const [dmg, bin] of single) { - let p = 0; - for (const label of wanted) p += bin.count[label] ?? 0; - total += dmg * p; - } - } - return total; - } - /** - * Returns damage statistics for scenarios where AT LEAST ONE attack results in - * the specified outcome(s). - * - * This method answers "What happens when things go reasonably well?" rather than - * "What's the theoretical maximum?" It includes mixed scenarios which are more - * common and tactically relevant than pure scenarios. - * - * Single label examples: - * - damageStatsFrom('hit') = damage range when at least one attack hits - * - damageStatsFrom('crit') = damage range when at least one attack crits - * - * Array examples: - * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds - * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses - * - * Tactical Use Cases: - * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" - * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" - * - "Should I use this risky spell if it has good damage when it works?" - * - "What's my damage potential when something goes right?" (vs pure failure) - * - * Combat Planning Examples: - * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" - * (Much more useful than "You average 50 damage including complete misses") - * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" - * - Resource management: "If I hit anything, I'll likely finish this enemy" - * - * Statistical Note: - * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which - * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. - * - * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an - * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and - * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than - * E[dmg | the label occurs]. For a single attack both are the plain - * conditional figures. Use {@link probAtLeastOne} for the scenario probability. - * - * @example - * // High-level tactical planning - * const successStats = query.damageStatsFrom('hit') - * const successChance = query.probAtLeastOne('hit') - * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) - */ - damageStatsFrom(labels) { - const labelArray = typeof labels === "string" ? [labels] : labels; - let minDamage = Infinity; - let maxDamage = -Infinity; - let totalDamage = 0; - let totalCount = 0; - for (const [damage, probabilityBin] of this.combined) { - let binHasAnyLabel = false; - let binContribution = 0; - for (const label of labelArray) { - const count = probabilityBin.count[label]; - if (count && count > 0) { - binHasAnyLabel = true; - binContribution += count; - } - } - if (damage > 0 && binHasAnyLabel) { - minDamage = Math.min(minDamage, damage); - maxDamage = Math.max(maxDamage, damage); - const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; - totalDamage += damage * weightToUse; - totalCount += weightToUse; - } - } - return { - min: minDamage === Infinity ? 0 : minDamage, - max: maxDamage === -Infinity ? 0 : maxDamage, - avg: totalCount > 0 ? totalDamage / totalCount : 0, - count: totalCount - }; - } - /** - * Returns damage statistics for scenarios where ALL attacks result in the specified - * outcome, calculated by leveraging the pure partition of singles. - * - * This method answers "What's the theoretical best/worst case?" and "What are the - * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. - * - * Examples: - * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) - * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) - * - * UI and Display Use Cases: - * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) - * - "Best case scenario" vs "worst case scenario" analysis - * - Mathematical verification: "Does our hit damage calculation match manual math?" - * - Clean damage type attribution: "How much comes from base hits vs crits?" - * - * Design and Balance Use Cases: - * - Game designers: "What's the damage ceiling if someone gets lucky?" - * - Character optimization: "What's my absolute maximum potential?" - * - Ability comparison: "Which build has higher crit ceiling?" - * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" - * - * Mathematical Use Cases: - * - Validating complex calculations against simple manual math - * - Understanding damage component contributions in isolation - * - Separating luck (crit variance) from consistency (hit variance) - * - Building intuition about damage sources - * - * When to Use This vs damageStatsFrom(): - * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons - * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios - * - * Statistical Note: - * Pure scenarios (all hits, all crits) are rare but represent clear mathematical - * boundaries. These stats help understand the "shape" of your damage potential. - * - * @example - * // UI display logic - * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" - * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" - * - * // vs tactical planning (use damageStatsFrom instead) - * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios - */ - combinedDamageStats(targetLabel) { - const singleStats = this.singles.map( - (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) - ); - if (singleStats.some((stats) => stats.count === 0)) { - return { min: 0, max: 0, avg: 0, count: 0 }; - } - const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); - const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); - const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); - const combinedProb = singleStats.reduce( - (product, stats) => product * stats.count, - 1 - ); - return { - min: combinedMin, - max: combinedMax, - avg: combinedAvg, - count: combinedProb - }; - } - /** - * Returns the probability that at least one attack carries ANY of the - * specified labels (the marginal P(≥1) across the independent attacks). - * - * Examples: - * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) - * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) - * - * Use cases: - * - "What's the chance my resolution includes a success label?" - * - "How likely am I to get any hits or crits across all attacks?" - * - * Note: this must NOT be computed by summing `combined` bin probabilities. A - * single combined damage total is reachable by many outcome combinations and - * a bin can hold several labels at once, so summing `bin.p` over bins that - * contain a label over-counts. The correct marginal is the Poisson-binomial - * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. - */ - probabilityOf(labels) { - return this.probAtLeastOne(labels); - } - /** - * Returns the probability of missing (any type of miss). - * - * Example: `query.missChance()` → 0.04 - * Use case: "What's the chance I miss completely this turn?" - */ - missChance() { - return this.probabilityOf(["missDamage", "missNone"]); - } - /** - * Returns data formatted for plotting damage probability distribution. - * - * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] - * Use case: "I want to visualize my damage distribution in a chart." - */ - toChartSeries() { - return this.combined.support().map((damageValue) => ({ - x: damageValue, - y: this.combined.map.get(damageValue).p - })); - } - /** - * Returns tabular data showing damage values and their probability breakdowns. - * - * Example: `query.toLabeledTable(['hit', 'crit'])` → - * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] - * - * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." - */ - toLabeledTable(labels = []) { - return this.combined.support().map((damageValue) => { - const probabilityBin = this.combined.map.get(damageValue); - const tableRow = { - damage: damageValue, - total: probabilityBin.p - }; - for (const outcomeLabel of labels) { - tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; - } - return tableRow; - }); - } - /** - * Returns data for stacked charts with unconditional per-label probability mass per damage. - * - * - Each dataset value equals the unconditional probability mass for that label at that damage - * (i.e., `bin.count[label]`). - * - Column sums may be less than the total probability `bin.p` when you omit labels or when - * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. - * - This behavior matches tests that expect raw per-label mass (not proportional scaling). - * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. - * - * @example - * query.toStackedChartData(['hit', 'crit']) - * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} - */ - toStackedChartData(labels = [], epsilon = EPS) { - const damageValues = this.combined.support(); - const datasets = labels.map((outcomeLabel) => ({ - label: outcomeLabel, - data: damageValues.map((dmg) => { - const bin = this.combined.map.get(dmg); - const v = bin ? bin.count[outcomeLabel] || 0 : 0; - return v <= epsilon ? 0 : v; - }) - })); - return { labels: damageValues, datasets }; - } - /** - * Returns pure mathematical data for attribution charts showing outcome contributions. - * - * Automatically discovers all outcome types present in the PMF, applies filtering rules, - * and returns proportional data suitable for stacked visualization. - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and proportional data - * - * @example - * query.toAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} - */ - toAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeCount = bin.count[outcome] || 0; - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - const outcomeProbability = bin.p * outcomeFraction; - return asPercentages ? outcomeProbability * 100 : outcomeProbability; - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for damage attribution charts showing damage contribution - * from each outcome type at each damage value. - * - * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of - * bin.count (probability attribution). - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) - * @returns Pure data structure with support, outcomes, and damage attribution data - * - * @example - * query.toDamageAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} - */ - toDamageAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - if (bin.attr) { - for (const outcomeType in bin.attr) { - if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin || !bin.attr) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeDamageAttribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { - if (filterRules(outcomeName, damage)) { - totalDamageAttribution += damageAttr || 0; - } - } - if (totalDamageAttribution === 0) return 0; - const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; - return damagePercentage * bin.p * 100; - } else { - return outcomeDamageAttribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for outcome attribution charts showing which - * attack outcome combinations can produce each damage value. - * - * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks - * outcome combinations - answering "what attack outcomes produced this damage?" - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and outcome combination probabilities - * - * @example - * query.toOutcomeAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} - */ - toOutcomeAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - if (outcome === "missNone") { - const outcomeCount = bin.count[outcome] || 0; - if (outcomeCount === 0) return 0; - if (asPercentages) { - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - return outcomeFraction * bin.p * 100; - } else { - return outcomeCount; - } - } - if (!bin.attr) return 0; - const outcomeDamageContribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [, damageAttr] of Object.entries(bin.attr)) { - totalDamageAttribution += damageAttr || 0; - } - if (totalDamageAttribution === 0) return 0; - const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; - return outcomeFraction * bin.p * 100; - } else { - return outcomeDamageContribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for cumulative distribution function (CDF). - * Shows P(X ≤ x) - the probability of getting at most x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and cumulative probabilities - * - * @example - * query.toCDFSeries() - * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} - */ - toCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const cdfData = []; - for (const damage of support) { - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - cdfData.push( - asPercentages ? cumulativeProbability * 100 : cumulativeProbability - ); - } - return { - support, - data: cdfData - }; - } - /** - * Returns pure mathematical data for complementary cumulative distribution function (CCDF). - * Shows P(X ≥ x) - the probability of getting at least x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and complementary cumulative probabilities - * - * @example - * query.toCCDFSeries() - * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} - */ - toCCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const ccdfData = []; - for (const damage of support) { - const ccdf = 1 - cumulativeProbability; - ccdfData.push(asPercentages ? ccdf * 100 : ccdf); - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - } - return { - support, - data: ccdfData - }; - } - /* - Statistics snapshot of the query. - */ - /** Probability of doing strictly more than threshold damage (default >0). */ - probDamageGreaterThan(threshold = 0) { - let acc = 0; - for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; - return acc; - } - /** All outcome keys actually present (typed & ordered if you pass an order). */ - outcomeKeys(order) { - const found = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) - if (bin.count[k] && bin.count[k] > 0) found.add(k); - } - if (found.size === 0) - ["hit", "crit", "missNone"].forEach((k) => found.add(k)); - const keys = Array.from(found).filter( - (k) => order?.includes(k) ?? true - ); - if (order && order.length) - keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); - return keys; - } - /** Total probability per outcome across the PMF. */ - outcomeTotals(outcomes = this.outcomeKeys()) { - const totals = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => totals.set(o, 0)); - for (const [, row] of this.combined.map) { - for (const o of outcomes) { - const p = row.count[o] || 0; - totals.set(o, (totals.get(o) || 0) + p); - } - } - return totals; - } - /** Conditional damage range per outcome (min/avg/max of X | outcome). */ - outcomeDamageRanges(outcomes = this.outcomeKeys()) { - const table = this.toLabeledTable(outcomes); - const ranges = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); - for (const row of table) { - const dmg = row.damage; - for (const o of outcomes) { - const p = row[o] || 0; - if (p > 0) { - const r = ranges.get(o); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - } - const out = /* @__PURE__ */ new Map(); - for (const o of outcomes) { - const r = ranges.get(o); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); - } - return out; - } - /** - * Snapshot of the distribution in the exact shape the UI consumes. - * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) - * - damageRange is conditional on the outcome occurring - * - * The outcome probabilities use the correct Poisson-binomial marginals - * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), - * so they are always valid probabilities in [0,1]. - * - * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from - * the combined PMF's `count`, which the convolution accumulates as an EXPECTED - * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] - * rather than a clean conditional expectation. It is correct for a single - * attack. - */ - snapshot(order) { - const discovered = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) { - if (bin.count[k] && bin.count[k] > 0) discovered.add(k); - } - } - if (discovered.size === 0) { - for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); - } - let outcomes = Array.from(discovered); - if (order && order.length) { - const inOrder = new Set(order); - outcomes = outcomes.filter((k) => inOrder.has(k)); - const rank = new Map(order.map((k, i) => [k, i])); - outcomes.sort( - (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) - ); - } - const rows = this.toLabeledTable(outcomes); - const rangeAcc = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - rangeAcc.set(ot, { sum: 0, mass: 0 }); - } - for (const row of rows) { - const dmg = row.damage; - for (const ot of outcomes) { - const p = row[ot] || 0; - if (p <= 0) continue; - const r = rangeAcc.get(ot); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - const n = this.singles.length; - const outcomeMap = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - const r = rangeAcc.get(ot); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - outcomeMap.set(ot, { - atLeastOneProbability: this.probAtLeastOne(ot), - allProbability: this.probAtLeastK(ot, n), - damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } - }); - } - const averageDPR = this.mean(); - let damageChance = 0; - for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; - const { support, data } = this.toCDFSeries(false); - const quantile = (p) => { - if (support.length === 0) return 0; - for (let i = 0; i < support.length; i++) - if (data[i] >= p) return support[i]; - return support[support.length - 1]; - }; - const percentiles = { - p25: quantile(0.25), - p50: quantile(0.5), - p75: quantile(0.75) - }; - return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; - } - /** - * PMF Transformation Methods - * - * These methods provide a fluent API for transforming dice queries by wrapping - * the underlying PMF transformation methods. All operations work on the combined - * PMF and return new DiceQuery instances. - */ - /** - * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). - * - * @returns New DiceQuery with normalized combined PMF - */ - normalize() { - return new _DiceQuery([this.combined.normalize()]); - } - /** - * Returns a new DiceQuery with low-probability outcomes removed. - * - * @param eps Minimum probability threshold (defaults to PMF epsilon) - * @param keepFinalBin Whether to keep the highest damage bin regardless of probability - * @returns New DiceQuery with compacted combined PMF - */ - compact(eps, keepFinalBin) { - return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); - } - /** - * Returns a new DiceQuery with an additional scaled branch added. - * Useful for conditional outcomes like "30% chance of opportunity attack". - * - * @param branch DiceQuery to add as a scaled branch - * @param probability Probability of the branch occurring (0-1) - * @returns New DiceQuery combining this query with the scaled branch - * - * @example - * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); - * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); - */ - addScaled(branch, probability) { - return new _DiceQuery([ - this.combined.addScaled(branch.combined, probability) - ]); - } - /** - * Returns a new DiceQuery with all probabilities scaled by a factor. - * Used for conditional scenarios where the entire outcome has reduced probability. - * - * @param factor Scaling factor for probabilities - * @returns New DiceQuery with scaled probabilities - * - * @example - * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario - */ - scaleMass(factor) { - return new _DiceQuery([this.combined.scaleMass(factor)]); - } - totalMass() { - return this.combined.mass(); - } - /** - * Returns a new DiceQuery with damage values transformed by a function. - * Useful for applying modifiers, resistances, or other damage transformations. - * - * @param damageTransformFunction Function to transform each damage value - * @returns New DiceQuery with transformed damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage - * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage - */ - mapDamage(damageTransformFunction) { - return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); - } - /** - * Returns a new DiceQuery with damage values scaled by a factor. - * Convenient wrapper around mapDamage for multiplicative scaling. - * - * @param factor Scaling factor for damage values - * @param rounding Rounding method: "floor" (default), "round", or "ceil" - * @returns New DiceQuery with scaled damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const doubled = baseAttack.scaleDamage(2); // Double damage - * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded - */ - scaleDamage(factor, rounding = "floor") { - return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); - } - /** - * Returns a new DiceQuery combining this query with another via convolution. - * Equivalent to rolling both queries independently and adding results. - * It is important to use this rather than combing()ing the PMFs directly! - * This method maintains the provenance of the PMFs which is needed for damage attribution. - * Combining the .combined PMFs directly is still valid for DPR calculations but - * is not statistically sound for queries. - * - * @param other DiceQuery to combine with - * @param eps Optional epsilon for precision control - * @returns New DiceQuery representing the combined outcome - * - * @example - * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); - * const bothAttacks = mainAttack.convolve(bonusAttack); - */ - convolve(other) { - const singles = [...this.singles, ...other.singles]; - return new _DiceQuery(singles); - } - /** - * First-success split over an ordered list of DISTINCT single-swing PMFs. - * Each PMF may have different success/subset probabilities (from labels). - * - * successOutcome: e.g., ["success"] or ["hit", "crit"] - * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success - * - * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] - */ - firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { - const pmfs = this.singles; - if (!pmfs.length) { - throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); - } - const toArr = (x) => Array.isArray(x) ? x : [x]; - const clamp01 = (x) => Math.max(0, Math.min(1, x)); - const tol = Math.max(eps, 8 * Number.EPSILON); - const per = pmfs.map((pmf) => { - const dq = new _DiceQuery([pmf]); - const pS = dq.probAtLeastOne(toArr(successOutcome)); - const pB = dq.probAtLeastOne(toArr(subsetOutcome)); - if (pB - pS > eps) { - throw new Error( - "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." - ); - } - return { pS, pB }; - }); - let missSoFar = 1; - let pFirstSubset = 0; - let pFirstNonSubset = 0; - let pNone = 1; - for (const { pS, pB } of per) { - pFirstSubset += missSoFar * pB; - pFirstNonSubset += missSoFar * (pS - pB); - const miss = 1 - pS; - missSoFar *= miss; - pNone *= miss; - } - const pAny = 1 - pNone; - const a = clamp01(pFirstNonSubset); - const b = clamp01(pFirstSubset); - const any = clamp01(pAny); - const none = clamp01(pNone); - if (Math.abs(a + b - any) > tol * Math.max(1, any)) { - throw new Error( - `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` - ); - } - return [a, b, any, none]; - } -}; -_DiceQuery.DEFAULT_OUTCOMES = [ - "hit", - "crit", - "missNone" -]; -var DiceQuery = _DiceQuery; -var pmfCache = new LRUCache(1e3); -var _PMF = class _PMF { - constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { - this.map = map; - this.epsilon = epsilon; - this.normalized = normalized; - this.identifier = identifier; - this._preservedProvenance = _preservedProvenance; - } - static empty(epsilon = EPS, identifier = "empty") { - return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); - } - // This has a single bin at value 0, mass of 1 - static zero(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "zero"); - } - static delta(value, epsilon = EPS) { - return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); - } - /** - * Point mass at damage 0 tagged with the canonical `missNone` outcome. - * - * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the - * builder's attack-resolution vocabulary. This uses the `missNone` - * {@link OutcomeType} that the attribution charts and outcome stats key on, - * so it is the correct "clean miss / no damage" delta for provenance-aware - * mixtures feeding those consumers. - */ - static missNone(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "missNone"); - } - // This creates a single bin at value 0, but with weight 0. - static emptyMass() { - return _PMF.zero().scaleMass(0); - } - // Makes PMF iterable over [damage, bin] pairs. - [Symbol.iterator]() { - return this.map[Symbol.iterator](); - } - static clearCache() { - pmfCache.clear(); - } - /** - * Creates a conditional PMF from two branches (success and failure) and a probability. - * This is the core logic for modeling any probabilistic event where there are two - * distinct outcomes. - */ - static branch(successPMF, failurePMF, successProbability) { - let p = successProbability; - if (!Number.isFinite(p)) p = 0; - if (p < 0) p = 0; - if (p > 1) p = 1; - const q = 1 - p; - if (p === 0) return failurePMF.scaleMass(1); - if (p === 1) return successPMF.scaleMass(1); - const eps = successPMF.epsilon ?? failurePMF.epsilon; - const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; - const resultMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of failurePMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); - } - for (const [damageValue, bin] of successPMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); - } - return new _PMF(resultMap, eps, false, id); - } - /** - * withProbability() - * - * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). - * - * Think of this as a shortcut for: - * pmf.gate(p, PMF.zero()) - * - * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, - * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. - * - * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. - * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. - * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) - * use query.firstSuccessSplit() to get the exact probabilities. - * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). - * - */ - static withProbability(successPMF, probability) { - return _PMF.branch(successPMF, _PMF.zero(), probability); - } - /** - * gate() - * - * A conditional wrapper around branch() that applies this PMF with probability `p`, - * and applies a provided fallback PMF otherwise. - * - * This is useful for modeling a binary choice between two outcomes: - * - The "success" outcome (this PMF) happens with probability `p`. - * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. - * - * Examples: - * - 25% chance to include an opportunity attack, otherwise nothing: - * attackPMF.gate(0.25, PMF.zero()) - * - * - 50% chance to deal fireball damage, otherwise cone of cold damage: - * fireballPMF.gate(0.5, coneOfColdPMF) - * - * Relationship to other helpers: - * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. - * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). - * - * @param p Probability of applying this PMF (between 0 and 1). - * @param fallback PMF to apply when this PMF is *not* selected. - * @returns A new PMF representing the weighted mixture of this PMF and the fallback. - */ - gate(p, fallback) { - return _PMF.branch(this, fallback, p); - } - /** - * PMF.exclusive() - * - * Builds a single PMF from a set of mutually exclusive weighted outcomes. - * Exactly one of the provided options will occur. - * - * Each option has: - * - A PMF representing its outcome (e.g., damage dice). - * - A weight representing its probability of being selected. - * - * Notes: - * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for floating point rounding. - */ - static exclusive(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight) || weight < -eps) { - throw new Error(`PMF.exclusive: invalid weight ${weight}.`); - } - } - let totalWeight = items.reduce((s, { weight }) => s + weight, 0); - if (Math.abs(totalWeight) <= eps) totalWeight = 0; - if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; - if (totalWeight > 1 + EPS) { - throw new Error( - `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` - ); - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (weight > eps) out = out.addScaled(pmf, weight); - } - const leftover = Math.max(0, 1 - totalWeight); - if (leftover > eps) { - out = out.addScaled(_PMF.zero(), leftover); - } - return out; - } - /** - * PMF.mix() - * - * Builds a PMF as a linear combination of input PMFs with the given weights. - * Unlike `exclusive`, this does NOT: - * - enforce that weights sum to 1 - * - add leftover probability to δ0 (PMF.zero()) - * - * Use when outcomes are not mutually exclusive, or for interpolation/blending. - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for skipping tiny weights. - */ - static mix(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight)) { - throw new Error(`PMF.mix: invalid weight ${weight}.`); - } - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (Math.abs(weight) <= eps) continue; - out = out.addScaled(pmf, weight); - } - return out; - } - /** - * Adds damage attribution metadata to this PMF based on existing count metadata. - * For each bin, sets attr[outcome] = damage × count[outcome]. - * - * This enables damage attribution charts to work with builder-generated PMFs. - * The parser generates attr automatically, but builder PMFs only have count. - * - * @returns New PMF with attr field populated in each bin - */ - /** - * Returns true if this PMF already carries damage attribution metadata. - * - * Only the first positive-damage bin is inspected (parser-generated PMFs - * populate `attr` uniformly), so this is O(1) in practice. - */ - hasAttribution() { - for (const [damage, bin] of this.map) { - if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { - return true; - } - if (damage > 0) break; - } - return false; - } - withAttribution() { - if (this.hasAttribution()) return this; - const newMap = /* @__PURE__ */ new Map(); - for (const [damage, bin] of this.map) { - const attr = {}; - for (const outcome in bin.count) { - const probability = bin.count[outcome]; - if (probability > 0) { - attr[outcome] = damage * probability; - } - } - newMap.set(damage, { - p: bin.p, - count: { ...bin.count }, - attr: Object.keys(attr).length > 0 ? attr : void 0 - }); - } - return new _PMF( - newMap, - this.epsilon, - this.normalized, - `${this.identifier}~attr` - ); - } - /** - * General-purpose N-way mixture. - * weights: Array of [weight, PMF]. - * - * Example: PMF.mixN([ - * [pMiss, zero], - * [pHit, hitPMF], - * [pCrit, critPMF], - * ]); - */ - static mixN(weights, eps = EPS) { - const filtered = weights.filter(([w]) => w > eps); - if (filtered.length === 0) { - return _PMF.emptyMass(); - } - let acc = null; - let sum = 0; - for (const [w, pmf] of filtered) { - if (acc === null) { - acc = pmf; - sum = w; - } else { - const q = w / (sum + w); - acc = _PMF.branch(pmf, acc, q); - sum += w; - } - } - return acc ?? _PMF.emptyMass(); - } - // This is a convenience method for when we use power - // TODO: It can be smarter in the future, and we can also add it to query - // That way statistics operations on invalid PMFs can throw an error - // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? - setPreservedProvenance(preserved) { - if (!this._preservedProvenance && preserved) { - throw new Error( - "Preserved provenance is already set to false, cannot fix that" - ); - } - this._preservedProvenance = preserved; - } - preservedProvenance() { - return this._preservedProvenance; - } - getPowerCacheKey(n, eps) { - const id = this.identifier; - let key = `${id}`; - for (let i = 1; i < n; i++) key += `+${id}`; - return `${key}@${eps}`; - } - /** - * Efficiently computes this PMF convolved with itself `n` times. - * Uses exponentiation by squaring to reduce total convolutions. - * n must be a positive integer. - * * - * * NOTE: This folds multiple independent attacks into a single PMF. - * As a result, The power() method causes a loss of data provenance. - * This is ONLY SAFE if you are trying to calculate masses. - * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). - */ - power(n, eps = this.epsilon) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("power(n): n must be a positive integer"); - } - if (n === 1) return this; - const epsilon = eps ?? this.epsilon; - const key = this.getPowerCacheKey(n, epsilon); - { - const cached = pmfCache?.get(key); - if (cached) return cached; - } - let base = this.normalized ? this : this.normalize(); - let result = base; - let exp = n - 1; - while (exp > 0) { - if (exp & 1) { - result = result.convolve(base, epsilon); - } - exp >>= 1; - if (exp > 0) { - base = base.convolve(base, epsilon); - } - } - result.setPreservedProvenance(false); - { - pmfCache?.set(key, result); - } - return result; - } - /* - * Helper for chaining multiple identical attacks - */ - replicate(n) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("replicate(n): n must be a positive integer"); - } - if (n === 1) return [this]; - return Array.from({ length: n }, () => this); - } - mass() { - if (this._totalMass === void 0) { - let totalProbabilityMass = 0; - for (const { p } of this.map.values()) { - totalProbabilityMass += p; - } - this._totalMass = totalProbabilityMass; - } - return this._totalMass; - } - outcomeMass(outcome) { - let totalProbabilityMass = 0; - for (const { p, count } of this.map.values()) { - totalProbabilityMass += p * (count[outcome] ?? 0); - } - return totalProbabilityMass; - } - // Helper for testing - faceTotal() { - return [...this.map.keys()].reduce((sum, key) => sum + key, 0); - } - normalize() { - if (this.normalized) return this; - const normalizationFactor = this.mass(); - if (normalizationFactor === 0) return this; - const normalizedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const normalizedCount = {}; - for (const labelKey in probabilityBin.count) { - normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; - } - let normalizedAttributes; - if (probabilityBin.attr) { - normalizedAttributes = {}; - for (const labelKey in probabilityBin.attr) { - normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; - } - } - normalizedMap.set(damageValue, { - p: probabilityBin.p / normalizationFactor, - count: normalizedCount, - attr: normalizedAttributes - }); - } - return new _PMF(normalizedMap, this.epsilon, true, this.identifier); - } - /** - * Returns a copy with negligible probabilities removed (p < eps). - * If keepFinalBin is true, the bin with the largest key is always kept, - * even if its probability is below eps. count/attr submaps are still cleaned. - */ - compact(eps = this.epsilon, keepFinalBin = false) { - let maxKey = -Infinity; - if (keepFinalBin) { - for (const key of this.map.keys()) { - if (key > maxKey) maxKey = key; - } - } - const compactedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; - if (!shouldKeep) continue; - const cleanedBin = _PMF.cloneBin(probabilityBin); - for (const labelKey in cleanedBin.count) { - if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { - delete cleanedBin.count[labelKey]; - } - } - if (cleanedBin.attr) { - for (const labelKey in cleanedBin.attr) { - if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { - delete cleanedBin.attr[labelKey]; - } - } - if (Object.keys(cleanedBin.attr).length === 0) { - cleanedBin.attr = void 0; - } - } - compactedMap.set(damageValue, cleanedBin); - } - return new _PMF(compactedMap, eps, this.normalized, this.identifier); - } - // Note: The "support" of a PMF is the set of all non-zero probability outcomes. - // This returns all damage values with non-zero probability, sorted ascending. - support() { - if (this._support === void 0) { - this._support = [...this.map.keys()].sort((a, b) => a - b); - } - return this._support; - } - // Minimum possible damage value. - min() { - if (this._min === void 0) { - const support = this.support(); - this._min = support.length > 0 ? support[0] : 0; - } - return this._min; - } - // Maximum possible damage value. - max() { - if (this._max === void 0) { - const support = this.support(); - this._max = support.length > 0 ? support[support.length - 1] : 0; - } - return this._max; - } - /** - * Returns the expected (mean) damage value. - * Cached for performance since this requires iterating through all bins. - */ - mean() { - if (this._mean === void 0) { - let totalSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - totalSum += damageValue * probabilityBin.p; - } - this._mean = totalSum; - } - return this._mean; - } - /** - * Returns the variance of the damage distribution. - * Cached for performance since this requires mean calculation plus iteration. - */ - variance() { - if (this._variance === void 0) { - const meanValue = this.mean(); - let varianceSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - const deviationFromMean = damageValue - meanValue; - varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; - } - this._variance = varianceSum; - } - return this._variance; - } - /** - * Returns the standard deviation of the damage distribution. - */ - stdev() { - if (this._stdev === void 0) { - this._stdev = Math.sqrt(this.variance()); - } - return this._stdev; - } - /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ - static cloneBin(bin) { - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ - static scaleBin(bin, factor) { - const count = {}; - for (const k in bin.count) { - count[k] = bin.count[k] * factor; - } - let attr; - if (bin.attr) { - attr = {}; - for (const k in bin.attr) { - attr[k] = bin.attr[k] * factor; - } - } - return { p: bin.p * factor, count, attr }; - } - static mergeInto(destinationMap, damageValue, binToAdd) { - const existingBin = destinationMap.get(damageValue); - if (!existingBin) { - destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); - return; - } - existingBin.p += binToAdd.p; - for (const labelKey in binToAdd.count) { - existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; - } - if (binToAdd.attr) { - if (!existingBin.attr) { - existingBin.attr = {}; - } - for (const labelKey in binToAdd.attr) { - existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; - } - } - } - // Convenience method - add(other) { - return this.addScaled(other, 1); - } - /** - * Returns a new PMF with a scaled branch added to this one. - * The branch PMF is scaled by the given probability before merging - * This will be very useful for conditional effects and for being - * able to model "I can probably have this opportunity attack 40% of rounds" - * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes - */ - addScaled(branch, probability) { - if (probability === 0) return this; - const resultMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of this.map) { - resultMap.set(dmg, _PMF.cloneBin(bin)); - } - for (const [damageValue, probabilityBin] of branch.map) { - _PMF.mergeInto( - resultMap, - damageValue, - _PMF.scaleBin(probabilityBin, probability) - ); - } - return new _PMF( - resultMap, - this.epsilon, - false, - `${this.identifier}+scaled(${branch.identifier},${probability})` - ); - } - /** - * Redistributes probability mass to model an effect that only occurs with - * probability `frequency` — a conditional attack, an on-hit rider, or a - * sub-one AoE target fraction. - * - * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, - * per-label `count`, AND per-label `attr` — and the freed mass is moved into - * the miss bin at damage 0, tagged with the canonical `missNone` outcome. - * Total probability mass is preserved. - * - * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage - * attribution (`attr`) intact, so a frequency-scaled PMF still renders - * correctly in the damage-attribution charts. - * - * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` - * collapses all mass into the miss bin. The miss outcome is assumed to be - * encoded at damage value 0. - * - * @param frequency Probability in [0, 1] that the effect occurs. - */ - applyHitFrequency(frequency) { - if (!Number.isFinite(frequency) || frequency >= 1) return this; - const freq = Math.max(0, frequency); - const pMiss = this.pAt(0); - const pHit = 1 - pMiss; - const newMissMass = pMiss + (1 - freq) * pHit; - const newMap = /* @__PURE__ */ new Map(); - newMap.set(0, { - p: newMissMass, - count: { [MISS_NONE_OUTCOME]: newMissMass }, - attr: {} - }); - for (const [damage, bin] of this.map) { - if (damage <= 0) continue; - newMap.set(damage, _PMF.scaleBin(bin, freq)); - } - return new _PMF( - newMap, - this.epsilon, - false, - `freq(${this.identifier},${freq})` - ); - } - scaleMass(factor) { - if (factor === 1) return this; - const scaledMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); - } - return new _PMF( - scaledMap, - this.epsilon, - false, - `scale(${this.identifier},${factor})` - ); - } - mapDamage(damageTransformFunction) { - const transformedMap = /* @__PURE__ */ new Map(); - for (const [originalDamage, probabilityBin] of this.map) { - const transformedDamage = damageTransformFunction(originalDamage); - _PMF.mergeInto( - transformedMap, - transformedDamage, - _PMF.cloneBin(probabilityBin) - ); - } - return new _PMF( - transformedMap, - this.epsilon, - this.normalized, - `map(${this.identifier})` - ); - } - scaleDamage(factor, rounding = "floor") { - const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; - return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); - } - getPMFCombineCacheKey(p1, p2, eps, raw) { - const [id1, id2] = [p1.identifier, p2.identifier].sort(); - return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; - } - /** - * A small content fingerprint (mass + bin count + face sum) so convolution - * cache keys change if the underlying numbers do. Memoized because a PMF is - * immutable once constructed — this avoids re-summing every key on each - * convolve() call (including cache hits). - */ - fingerprint() { - if (this._fingerprint === void 0) { - let faceSum = 0; - for (const k of this.map.keys()) faceSum += k; - this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; - } - return this._fingerprint; - } - convolve(other, eps, raw = false) { - const epsilon = eps ?? this.epsilon; - const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); - const A0 = norm(this); - const B0 = norm(other); - const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; - const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); - const cached = pmfCache?.get(cacheKey); - if (cached) return cached; - const combinedMap = /* @__PURE__ */ new Map(); - for (const [aVal, aBin] of A.map) { - const ap = aBin.p; - const aCount = aBin.count; - const aAttr = aBin.attr; - for (const [bVal, bBin] of B.map) { - const bp = bBin.p; - const dmg = aVal + bVal; - let dest = combinedMap.get(dmg); - if (dest === void 0) { - dest = { p: 0, count: {} }; - combinedMap.set(dmg, dest); - } - dest.p += ap * bp; - const dc = dest.count; - for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; - for (const k in bBin.count) - dc[k] = (dc[k] || 0) + bBin.count[k] * ap; - if (aAttr || bBin.attr) { - let da = dest.attr; - if (da === void 0) { - da = {}; - dest.attr = da; - } - if (aAttr) - for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; - if (bBin.attr) - for (const k in bBin.attr) - da[k] = (da[k] || 0) + bBin.attr[k] * ap; - } - } - } - let result = new _PMF( - combinedMap, - epsilon, - !raw, - `${A.identifier}${raw ? "*" : "+"}${B.identifier}` - ); - const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); - const mGot = result.mass(); - if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { - result = result.scaleMass(mExp / mGot); - } - if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) - result = result.normalize(); - pmfCache?.set(cacheKey, result); - return result; - } - // 3) Nice wrapper so you can call pmf.combineRaw(other) - combineRaw(other, eps) { - return this.convolve(other, eps, true); - } - // Reduce a list of PMFs by left-folding convolve() with the given eps - static reduceConvolveLeft(pmfList, eps) { - let result = pmfList[0]; - for (let i = 1; i < pmfList.length; i++) { - result = result.convolve(pmfList[i], eps); - } - return result; - } - /** - * Convolves multiple PMFs using linear convolution with automatic caching. - * Uses a left-to-right accumulation approach for maximum cache reuse. - * Each convolve() call automatically uses the convolution cache for performance. - * - * This linear approach provides better cache hits than pairwise because: - * - Intermediate results are more predictable and stable - * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) - * - Order-independent cache keys work better with consistent build patterns - */ - static convolveMany(pmfList, eps = EPS) { - if (pmfList.length === 0) return _PMF.empty(eps); - if (pmfList.length === 1) return pmfList[0]; - return _PMF.reduceConvolveLeft(pmfList, eps); - } - /** - * Returns a plain, JSON-serializable representation of this PMF. - * - * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces - * the expected output (no double-encoding). Use {@link PMF.fromJSON} to - * reconstruct, or {@link PMF.toJSONString} if you need the string directly. - */ - toJSON() { - return { - bins: [...this.map.entries()], - normalized: this.normalized, - identifier: this.identifier - }; - } - /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ - toJSONString() { - return JSON.stringify(this); - } - static fromJSON(jsonData) { - return new _PMF( - new Map(jsonData.bins), - EPS, - !!jsonData.normalized, - jsonData.identifier || "fromJSON" - ); - } - /** - * Relative pruning with optional top-K floor. - * Keeps bins with p >= epsRel * peak, always keeps min and max damage, - * optionally guarantees at least `minBins` survivors by adding top-K. - * Returns a new, non-normalized PMF. - */ - prune(epsRel, minBins = 0) { - const size = this.map.size; - if (size === 0) return this; - let peak = 0; - let minDamage = Number.POSITIVE_INFINITY; - let maxDamage = Number.NEGATIVE_INFINITY; - for (const [dmg, bin] of this.map) { - if (bin.p > peak) peak = bin.p; - if (dmg < minDamage) minDamage = dmg; - if (dmg > maxDamage) maxDamage = dmg; - } - if (peak === 0) - return new _PMF(new Map(this.map), epsRel, false, this.identifier); - const thresh = epsRel * peak; - const entries = [...this.map.entries()]; - const survivorsByDmg = /* @__PURE__ */ new Map(); - const protect = (d2) => { - const b = this.map.get(d2); - if (b) survivorsByDmg.set(d2, b); - }; - protect(minDamage); - if (maxDamage !== minDamage) protect(maxDamage); - for (const [dmg, bin] of entries) { - if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); - } - if (minBins > 0 && survivorsByDmg.size < minBins) { - entries.sort((a, b) => b[1].p - a[1].p); - for (const [dmg, bin] of entries) { - if (!survivorsByDmg.has(dmg)) { - survivorsByDmg.set(dmg, bin); - if (survivorsByDmg.size >= minBins) break; - } - } - } - const prunedMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of survivorsByDmg) { - const newCount = {}; - for (const k in bin.count) { - const v = bin.count[k]; - if (Math.abs(v) >= thresh) newCount[k] = v; - } - let newAttr; - if (bin.attr) { - for (const k in bin.attr) { - const v = bin.attr[k]; - if (Math.abs(v) >= thresh) { - if (!newAttr) newAttr = {}; - newAttr[k] = v; - } - } - } - prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); - } - return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); - } - /** Probability mass at exactly x. */ - pAt(x) { - return this.map.get(x)?.p ?? 0; - } - /** - * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. - * Assumes a miss is encoded as the damage-0 bin (the convention used across - * attack/save PMFs). The dual of {@link missProbability}. - */ - hitProbability() { - return 1 - this.pAt(0); - } - /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ - missProbability() { - return this.pAt(0); - } - /** - * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width - * damage buckets, aggregating probability mass (and `count`/`attr` - * provenance) into each bucket's start value. Returns this PMF unchanged when - * its integer support already fits within `maxBuckets`. - * - * This is a lossy display/downsampling transform (bucket start replaces the - * exact damage value) — use it for charting wide distributions, not for DPR - * math. - */ - rebin(maxBuckets) { - if (!(maxBuckets > 0)) return this; - const support = this.support(); - if (support.length === 0) return this; - const min = support[0]; - const max = support[support.length - 1]; - const range = max - min; - if (range + 1 <= maxBuckets) return this; - const binSize = Math.ceil((range + 1) / maxBuckets); - return this.mapDamage((d2) => min + Math.floor((d2 - min) / binSize) * binSize); - } - /** Dense integer support from min..max (inclusive). - * Useful for showing empty bars in charts. - */ - denseSupport() { - const s = this.support(); - if (s.length === 0) return []; - const lo = Math.min(...s), hi = Math.max(...s); - return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( - (a, b) => a - b - ); - } - /** CDF at x: P(X ≤ x). */ - cdfAt(x) { - let acc = 0; - for (const [val, bin] of this.map) if (val <= x) acc += bin.p; - return acc; - } - /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ - quantile(p) { - if (this.map.size === 0) return 0; - const s = this.support().sort((a, b) => a - b); - let acc = 0; - for (const x of s) { - acc += this.pAt(x); - if (acc >= p) return x; - } - return s[s.length - 1]; - } - /** Get outcome probability at specific damage value. */ - outcomeAt(damage, outcome) { - return this.map.get(damage)?.count[outcome] ?? 0; - } - /** Get all outcome types present in this PMF. */ - outcomes() { - const outcomeSet = /* @__PURE__ */ new Set(); - for (const [, bin] of this.map) { - for (const outcome in bin.count) { - if (bin.count[outcome] > 0) { - outcomeSet.add(outcome); - } - } - } - return Array.from(outcomeSet).sort(); - } - /** Get total probability of an outcome across all damage values. */ - outcomeProbability(outcome) { - let total = 0; - for (const [, bin] of this.map) { - total += bin.count[outcome] ?? 0; - } - return total; - } - /** Get damage attribution for an outcome at specific damage value. */ - outcomeAttributionAt(damage, outcome) { - return this.map.get(damage)?.attr?.[outcome] ?? 0; - } - /** Get all outcome data at specific damage value. */ - binAt(damage) { - const bin = this.map.get(damage); - if (!bin) return null; - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Check if outcome exists in this PMF. */ - hasOutcome(outcome) { - for (const [, bin] of this.map) { - if ((bin.count[outcome] ?? 0) > 0) { - return true; - } - } - return false; - } - /** - * Split each damage value's probability mass across outcome labels, returning - * per-label maps of `damage value → probability mass attributable to that - * label`. Summing over labels at a given value recovers that value's `p`. - * - * Damage-bearing bins are split by `attr` weight (the share of damage each - * outcome contributed); the clean-miss bin at 0 is split by `count` weight - * (there is no damage to attribute). Attribution is computed on demand via - * {@link withAttribution} when absent, so builder-generated PMFs work too. - * - * This is the provenance core of the stacked damage-attribution chart — the - * caller only maps these series into its rendering format (colors, binning, - * axis labels). - */ - attributionByValue() { - const src = this.hasAttribution() ? this : this.withAttribution(); - const result = /* @__PURE__ */ new Map(); - const add = (label, damage, mass) => { - if (!(mass > 0)) return; - let series = result.get(label); - if (!series) { - series = /* @__PURE__ */ new Map(); - result.set(label, series); - } - series.set(damage, (series.get(damage) ?? 0) + mass); - }; - for (const [damage, bin] of src.map) { - const p = bin.p || 0; - if (p <= 0) continue; - const isMissBin = damage === 0; - if (isMissBin) { - let totalCount = 0; - for (const k in bin.count) totalCount += bin.count[k] || 0; - if (totalCount > 0) { - const c = bin.count[MISS_NONE_OUTCOME] || 0; - add(MISS_NONE_OUTCOME, damage, c / totalCount * p); - } - continue; - } - let totalAttr = 0; - if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; - if (bin.attr && totalAttr > 0) { - for (const k in bin.attr) { - if (k === MISS_NONE_OUTCOME) continue; - add(k, damage, (bin.attr[k] || 0) / totalAttr * p); - } - } - } - return result; - } - tailProbGE(t) { - let s = 0; - for (const [x, bin] of this) { - if (bin.p > 0 && x >= t) s += bin.p; - } - return s; - } - tailProbGT(t) { - let s = 0; - for (const [x, rec] of this) { - if (x > t) s += rec.p; - } - return s; - } - /** - * Returns a new PMF containing only bins where the specified outcome has non-zero probability. - * This creates a marginal distribution for the given outcome type, with probabilities - * scaled to represent the unconditional mass attributable to that outcome. - */ - filterOutcome(outcome) { - const filteredMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of this.map) { - const outcomeCount = bin.count[outcome] ?? 0; - const totalCount = Object.values(bin.count ?? {}).reduce( - (a, b) => (a ?? 0) + (b ?? 0), - 0 - ); - if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { - const proportion = outcomeCount / totalCount; - const newP = bin.p * proportion; - const newCount = { [outcome]: outcomeCount }; - let newAttr; - if (bin.attr && bin.attr[outcome] !== void 0) { - newAttr = { [outcome]: bin.attr[outcome] * proportion }; - } - filteredMap.set(damageValue, { - p: newP, - count: newCount, - attr: newAttr - }); - } - } - return new _PMF( - filteredMap, - this.epsilon, - false, - // don't normalize by default - `filter(${this.identifier},${outcome})` - ); - } - /** - * Calculates probabilities for first-success outcomes across n independent attempts. - * - * @param pSuccess - Total probability of any success on a single attempt. - * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). - * @param n - Number of independent attempts. - * - * Returns: - * - pSpecificSuccess: Probability that the first success was of the "special" type - * - pGeneralSuccess: Probability that the first success was of the non-special type - * - pNone: Probability that no successes occurred - * - pAny: Probability that at least one success occurred - */ - static firstSuccessWeights(pSuccess, pSpecial, n) { - if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { - throw new Error( - `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` - ); - } - const pFail = 1 - pSuccess; - const pFailAll = Math.pow(pFail, n); - const pAny = 1 - pFailAll; - const denom = pSuccess === 0 ? 1 : pSuccess; - const pSpecificSuccess = pSpecial * pAny / denom; - const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; - const pNone = 1 - pSpecificSuccess - pGeneralSuccess; - return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; - } - mapValues(f, eps = EPS, opts) { - const rounding = opts?.rounding ?? "none"; - const preserveCounts = opts?.preserveCounts ?? true; - const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; - const probs = /* @__PURE__ */ new Map(); - const counts = /* @__PURE__ */ new Map(); - for (const [v, bin] of this) { - if (Math.abs(bin.p) < eps) continue; - const u = round(f(v)); - probs.set(u, (probs.get(u) ?? 0) + bin.p); - if (preserveCounts) { - const src = bin.count; - if (src) { - const dest = counts.get(u) ?? {}; - for (const k in src) { - dest[k] = (dest[k] ?? 0) + src[k]; - } - counts.set(u, dest); - } - } - } - const internal = /* @__PURE__ */ new Map(); - for (const [u, p] of probs) { - internal.set(u, { p, count: counts.get(u) ?? {} }); - } - return _PMF.fromMap( - new Map(Array.from(internal, ([u, b]) => [u, b.p])), - eps - ); - } - static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { - const filtered = []; - for (const [v, p] of m) { - if (!Number.isFinite(v) || !Number.isFinite(p)) continue; - if (p <= 0 || Math.abs(p) < eps) continue; - if (requireIntegerValues && !Number.isInteger(v)) { - throw new Error(`fromMap: non-integer outcome ${v}`); - } - filtered.push([v, p]); - } - if (filtered.length === 0) { - throw new Error("fromMap: empty or invalid input map"); - } - let sum = 0; - let c = 0; - for (const [, p] of filtered) { - const y = p - c; - const t = sum + y; - c = t - sum - y; - sum = t; - } - if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); - filtered.sort((a, b) => a[0] - b[0]); - const internal = /* @__PURE__ */ new Map(); - for (const [v, p] of filtered) { - internal.set(v, { p: p / sum, count: {} }); - } - return new _PMF(internal, eps); - } - query() { - return new DiceQuery(this); - } -}; -// Unique ID generator for anonymous PMFs to avoid cache key collisions -_PMF.__anonIdCounter = 1; -var PMF = _PMF; - -// src/pmf/mixture.ts -var Mixture = class _Mixture { - constructor(eps = EPS) { - this.totals = /* @__PURE__ */ new Map(); - // raw mass per outcome (pre-normalization) - this.labelMass = /* @__PURE__ */ new Map(); - this.eps = Number.isFinite(eps) ? eps : EPS; - } - /** Remove all accumulated state. */ - clear() { - this.totals.clear(); - this.labelMass.clear(); - return this; - } - /** Number of distinct outcome values currently accumulated. */ - size() { - return this.totals.size; - } - /** Whether a label was ever added. */ - hasLabel(label) { - for (const bag of this.labelMass.values()) if (bag[label]) return true; - return false; - } - /** - * Add a labeled component with a mixture weight. - * Weight can be any positive finite number. Very small contributions are pruned by eps. - */ - add(label, pmf, weight = 1) { - if (!Number.isFinite(weight) || weight <= 0) return this; - for (const [v, bin] of pmf) { - const p = bin.p; - if (p <= 0) continue; - const add = weight * p; - if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; - this.totals.set(v, (this.totals.get(v) ?? 0) + add); - const bag = this.labelMass.get(v) ?? {}; - bag[label] = (bag[label] ?? 0) + add; - this.labelMass.set(v, bag); - } - return this; - } - buildPMF(eps = EPS) { - let grand = 0; - let c = 0; - for (const m of this.totals.values()) { - const y = m - c; - const t = grand + y; - c = t - grand - y; - grand = t; - } - if (!(grand > 0)) throw new Error("Mixture: zero total mass"); - const internal = /* @__PURE__ */ new Map(); - for (const [v, m] of this.totals) { - if (m <= 0 || Math.abs(m) < this.eps) continue; - const count = this.labelMass.get(v) ?? {}; - internal.set(v, { p: m / grand, count }); - } - return new PMF(internal, eps); - } - /** - * Produce normalized *per-label* PMFs (labels independent). - * These are unlabeled PMFs built from the raw mass of that label alone. - */ - byOutcome() { - const labels = /* @__PURE__ */ new Set(); - for (const bag of this.labelMass.values()) { - for (const k of Object.keys(bag)) labels.add(k); - } - const out = {}; - for (const label of labels) { - const m = /* @__PURE__ */ new Map(); - for (const [v, bag] of this.labelMass) { - const w = bag[label]; - if (w && Math.abs(w) >= this.eps) m.set(v, w); - } - if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); - } - return out; - } - /** - * Mixture weights per label, normalized to sum to 1 over labels that appeared. - * Uses raw mass before per-outcome normalization. - */ - weights() { - const res = {}; - for (const [, bag] of this.labelMass) { - for (const [lab, w] of Object.entries(bag)) { - if (!Number.isFinite(w) || w <= 0) continue; - res[lab] = (res[lab] ?? 0) + w; - } - } - let total = 0; - let c = 0; - for (const v of Object.values(res)) { - const y = v - c; - const t = total + y; - c = t - total - y; - total = t; - } - if (total > 0) { - for (const k in res) res[k] = res[k] / total; - } - return res; - } - toJSON() { - return { - totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), - labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), - eps: this.eps - }; - } - static mix(items, eps = EPS) { - const mix = new _Mixture(eps); - for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); - return mix.buildPMF(); - } -}; - -// src/common/errors.ts -var DiceParseError = class _DiceParseError extends Error { - constructor(message, options) { - super(message); - this.name = "DiceParseError"; - this.expression = options?.expression; - this.cause = options?.cause; - Object.setPrototypeOf(this, _DiceParseError.prototype); - } -}; - -// src/parser/dice.ts -var MAX_BINARY_OUTCOMES = 1e8; -var Dice = class _Dice { - constructor(x = 0) { - this.faces = {}; - this.privateData = {}; - // Partial: the object starts empty and gains keys as outcomes are recorded, - // so the type must not claim every OutcomeType is present. (Previously typed - // as a full Record via an `as` cast, which lied about missing keys.) - this.outcomeData = {}; - this.hasHitDistributionCalculated = false; - if (x <= 0) return; - for (let i = 1; i <= x; i++) { - this.faces[i] = 1; - } - } - getOutcomeDistribution(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const distribution = this.outcomeData[key]; - if (distribution === void 0) return void 0; - return { ...distribution }; - } - getFullOutcomeDistribution() { - return { ...this.outcomeData }; - } - setOutcomeDistribution(key, data) { - if (data) { - this.outcomeData[key] = data; - } else { - delete this.outcomeData[key]; - } - } - hasOutcomeData(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const data = this.outcomeData[key]; - return data !== void 0 && Object.keys(data).length > 0; - } - getOutcomeCount(key, face) { - return this.outcomeData[key]?.[face] ?? 0; - } - getAverage(key) { - const distribution = this.getOutcomeDistribution(key); - if (!distribution) return 0; - const totalCount = Object.values(distribution).reduce( - (sum, count) => sum + count, - 0 - ); - const expectedDamage = Object.entries(distribution).reduce( - (sum, [damage, count]) => sum + Number(damage) * count, - 0 - ); - if (totalCount === 0) return 0; - return expectedDamage / totalCount; - } - // TODO this can be private later if we change how testing works - calculateHitDistribution() { - const hitValues = {}; - const subtractedOutcomes = [ - this.outcomeData.crit, - this.outcomeData.missNone, - this.outcomeData.missDamage, - this.outcomeData.saveHalf, - this.outcomeData.saveFail, - this.outcomeData.pc - ]; - for (const [face, totalCount] of Object.entries(this.faces)) { - const numFace = Number(face); - let hitCount = totalCount; - for (const distribution of subtractedOutcomes) { - const outcomeCount = distribution?.[numFace]; - if (outcomeCount) { - hitCount -= outcomeCount; - } - } - if (numFace === 0) { - hitCount = 0; - } - if (hitCount < 0) { - hitCount = 0; - } - hitValues[numFace] = hitCount; - } - return hitValues; - } - ensureHitDistribution() { - if (!this.hasHitDistributionCalculated) { - const hitValues = this.calculateHitDistribution(); - this.setOutcomeDistribution("hit", hitValues); - this.hasHitDistributionCalculated = true; - } - } - // PRIVATE FUNCTIONS - binaryOp(other, op, diceConstructor) { - const result = diceConstructor ? diceConstructor() : new _Dice(); - const isScalar = typeof other === "number"; - const keys1 = this.keys(); - const keys2 = isScalar ? [] : other.keys(); - if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { - throw new DiceParseError( - `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` - ); - } - for (const key1 of keys1) { - const value1 = this.faces[key1]; - if (isScalar) { - const resultKey = op(key1, other); - result.increment(resultKey, value1); - } else { - for (const key2 of keys2) { - const value2 = other.faces[key2]; - const resultKey = op(key1, key2); - result.increment(resultKey, value1 * value2); - } - } - } - return result; - } - removeFaces(facesToRemove) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (!facesToRemove.includes(numKey)) { - result.faces[numKey] = value; - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // PUBLIC FUNCTIONS - getFaceEntries() { - return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); - } - getFaceMap() { - return { ...this.faces }; - } - get(face) { - return this.faces[face] ?? 0; - } - keys() { - return Object.keys(this.faces).map(Number); - } - values() { - return Object.values(this.faces); - } - total() { - return Object.values(this.faces).reduce((sum, value) => sum + value, 0); - } - setFace(key, value) { - this.faces[key] = value; - } - static scalar(value) { - const result = new _Dice(); - result.increment(value, 1); - return result; - } - maxFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.max(...numericKeys); - } - minFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.min(...numericKeys); - } - increment(face, count) { - const current = this.faces[face] || 0; - this.faces[face] = current + count; - } - normalize(scalar) { - const result = new _Dice(); - for (const [face, count] of Object.entries(this.faces)) { - result.faces[Number(face)] = count * scalar; - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // OPERATIONS - add(other) { - return this.binaryOp(other, (a, b) => a + b); - } - subtract(other) { - return this.binaryOp(other, (a, b) => a - b); - } - conditionalApply(other) { - return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); - } - multiply(other) { - return this.binaryOp(other, (a, b) => a * b); - } - addNonZero(other) { - return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); - } - eq(other) { - return this.binaryOp(other, (a, b) => a === b ? 1 : 0); - } - max(other) { - return this.binaryOp(other, (a, b) => Math.max(a, b)); - } - min(other) { - return this.binaryOp(other, (a, b) => Math.min(a, b)); - } - advantage() { - return this.max(this); - } - ge(other) { - return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); - } - divide(other) { - return this.binaryOp(other, (a, b) => a / b); - } - divideRoundUp(other) { - return this.binaryOp(other, (a, b) => Math.ceil(a / b)); - } - divideRoundDown(other) { - return this.binaryOp(other, (a, b) => Math.floor(a / b)); - } - and(other) { - return this.binaryOp(other, (a, b) => a && b ? 1 : 0); - } - checkTarget(other, comparisonLogic) { - const createResult = () => { - const result = new _Dice(); - result.increment(0, 0); - result.increment(1, 0); - return result; - }; - return this.binaryOp(other, comparisonLogic, createResult); - } - dc(other) { - const dcCheck = (a, b) => a >= b ? 0 : 1; - const result = this.checkTarget(other, dcCheck); - result.privateData.isDCCheck = true; - return result; - } - ac(other) { - const acCheck = (a, b) => a >= b ? a : 0; - return this.checkTarget(other, acCheck); - } - deleteFace(face) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (numKey !== face) { - result.increment(numKey, value); - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - reroll(toReroll) { - const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; - const rerollKeys = rerollDice.keys(); - const rerollSet = new Set(rerollKeys); - const removed = this.removeFaces(rerollKeys); - let result = new _Dice(); - for (const face of this.keys()) { - const wasRerolled = rerollSet.has(face); - result = result.combine(removed); - if (wasRerolled) { - result = result.combine(this); - } - } - return result; - } - // This is not addition and not rolling two dice at once. - // Instead, it’s mixing two distributions into a single weighted die. - combine(other) { - if (typeof other === "number") { - other = _Dice.scalar(other); - } - const result = new _Dice(); - for (const [key, value] of Object.entries(other.faces)) { - result.faces[Number(key)] = value; - } - const except = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - result.increment(numKey, value); - if (!(numKey in other.faces)) { - except.increment(numKey, value); - } - } - result.privateData = { ...this.privateData, except: other }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - combineInPlace(other) { - for (const [key, value] of Object.entries(other.faces)) { - const numKey = Number(key); - const current = this.faces[numKey] || 0; - this.faces[numKey] = current + value; - } - } - percent() { - const total = this.total(); - const result = {}; - for (const [face, count] of Object.entries(this.faces)) { - result[Number(face)] = count / total; - } - return result; - } - average() { - const total = this.total(); - if (total === 0) return 0; - let sum = 0; - for (const [key, value] of Object.entries(this.faces)) { - sum += Number(key) * value; - } - return sum / total; - } - /* - * Convert dice to PMF using OutcomeType labels directly from damage distribution. - * This is much cleaner than the original complex distribution conversion. - */ - toPMF(numEpsilon = EPS) { - const total = this.total(); - if (total === 0) return PMF.empty(numEpsilon); - this.ensureHitDistribution(); - const map = /* @__PURE__ */ new Map(); - const hitDistro = this.getOutcomeDistribution("hit") || {}; - const critDistro = this.getOutcomeDistribution("crit") || {}; - const missDistro = this.getOutcomeDistribution("missDamage") || {}; - const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; - const pcDistro = this.getOutcomeDistribution("pc") || {}; - const isSaveHalf = Object.keys(saveDistro).length > 0; - const isDCCheck = this.privateData.isDCCheck === true; - const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; - for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { - const face = Number(faceStr); - const faceCount = Number(faceCountRaw); - if (faceCount <= 0) continue; - let p = faceCount / total; - p = clampNonNeg(p); - if (!(p > 0)) continue; - if (numEpsilon >= 0 && p < numEpsilon) continue; - const count = {}; - const attr = {}; - if (hitDistro[face]) { - const c = clampNonNeg(hitDistro[face] / total); - if (c > 0) { - if (isSaveHalf || isDCCheck) { - count.saveFail = c; - attr.saveFail = clampNonNeg(face * hitDistro[face] / total); - } else { - count.hit = c; - attr.hit = clampNonNeg(face * hitDistro[face] / total); - } - } - } - if (critDistro[face]) { - const c = clampNonNeg(critDistro[face] / total); - if (c > 0) { - count.crit = c; - attr.crit = clampNonNeg(face * critDistro[face] / total); - } - } - if (missDistro[face]) { - const c = clampNonNeg(missDistro[face] / total); - if (c > 0) { - count.missDamage = c; - attr.missDamage = clampNonNeg(face * missDistro[face] / total); - } - } - if (saveDistro[face]) { - const c = clampNonNeg(saveDistro[face] / total); - if (c > 0) { - if (isSaveHalf) { - count.saveHalf = c; - attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); - } else { - count.saveFail = (count.saveFail ?? 0) + c; - attr.saveFail = clampNonNeg( - (attr.saveFail ?? 0) + face * saveDistro[face] / total - ); - } - } - } - if (pcDistro[face]) { - const c = clampNonNeg(pcDistro[face] / total); - if (c > 0) { - count.pc = c; - attr.pc = clampNonNeg(face * pcDistro[face] / total); - } - } - if (!isSaveHalf && !isDCCheck) { - const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); - const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); - if (unaccountedCount > 0) { - const frac = clampNonNeg(unaccountedCount / total); - if (frac > 0) { - count.missNone = (count.missNone ?? 0) + frac; - } - } - } - const bin = { p, count }; - if (Object.keys(attr).length > 0) { - bin.attr = attr; - } - map.set(face, bin); - } - const identifier = this.identifier || "ERROR"; - return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); - } -}; - -// src/parser/parser.ts -var MAX_DIE_SIDES = 1e6; -var MAX_DICE_COUNT = 1e4; -var MAX_KEEP_OUTCOMES = 1e6; -var parseCache = new LRUCache(1e3); -function parse(expression, n = 0) { - const cleaned = expression.replace(/ /g, "").toLowerCase(); - { - const cacheKey = `${cleaned}:${n}`; - const cached = parseCache.get(cacheKey); - if (cached) return cached; - } - const chars = [...cleaned]; - let result; - try { - result = parseExpression(chars, n); - } catch (error) { - throw new DiceParseError( - `Cannot parse dice expression [${expression}]: ${error}`, - { expression, cause: error } - ); - } - result.privateData = result.privateData || {}; - result.identifier = cleaned; - if (chars.length > 0) { - throw new DiceParseError( - `Unexpected token: '${chars[0]}' from expression: '${expression}'`, - { expression } - ); - } - const resultPMF = result.toPMF(-1); - { - const cacheKey = `${cleaned}:${n}`; - parseCache.set(cacheKey, resultPMF); - } - return resultPMF; -} -function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { - dice = dice.normalize(currentNorm); - finalResult = finalResult.normalize(normValue); - finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); - finalResult = finalResult.combine(dice); - return { newNorm: currentNorm * normValue, updatedResult: finalResult }; -} -function parseExpression(arr, n) { - const result = (() => { - const res = parseArgument(arr, n); - return typeof res === "number" ? Dice.scalar(res) : res; - })(); - let op = parseOperation(arr); - let finalResult = result; - while (op != null) { - const arg = !op.unary ? parseArgument(arr, n) : finalResult; - let crit; - let critNorm = 1; - if (arr[0] === "x" || arr[0] === "c") { - const isXcrit = arr[0] === "x"; - if (isXcrit) assertToken(arr, "x"); - assertToken(arr, "c"); - assertToken(arr, "r"); - assertToken(arr, "i"); - assertToken(arr, "t"); - const count = isXcrit ? parseNumber(arr, n) : 1; - crit = new Dice(); - for (let i = 0; i < count; i++) { - const max = finalResult.maxFace(); - crit.setFace(max, finalResult.get(max)); - finalResult = finalResult.deleteFace(max); - } - critNorm = crit.total(); - crit = op.call(crit, parseBinaryArgument(arg, arr, n)); - critNorm = crit && critNorm ? crit.total() / critNorm : 1; - } - let save; - let saveNorm = 1; - if (arr[0] === "s") { - assertToken(arr, "s"); - assertToken(arr, "a"); - assertToken(arr, "v"); - assertToken(arr, "e"); - save = new Dice(); - const min = finalResult.minFace(); - save.increment(min > 0 ? min : 1, finalResult.get(min)); - saveNorm = save.total(); - finalResult = finalResult.deleteFace(min); - save = op.call(save, parseBinaryArgument(arg, arr, n)); - saveNorm = save && saveNorm ? save.total() / saveNorm : 1; - } - let pc; - let pcNorm = 1; - if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { - assertToken(arr, "p"); - assertToken(arr, "c"); - pc = new Dice(); - const min = finalResult.minFace(); - pc.increment(min > 0 ? min : 1, finalResult.get(min)); - const missBefore = pc.total(); - finalResult = finalResult.deleteFace(min); - pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); - const missAfter = pc ? pc.total() : 0; - pcNorm = missBefore ? missAfter / missBefore : 1; - } - let miss; - let missNorm = 1; - if (arr[0] === "m") { - assertToken(arr, "m"); - assertToken(arr, "i"); - assertToken(arr, "s"); - assertToken(arr, "s"); - miss = new Dice(); - const min = finalResult.minFace(); - miss.increment(min > 0 ? min : 1, finalResult.get(min)); - missNorm = miss.total(); - finalResult = finalResult.deleteFace(min); - miss = op.call(miss, parseBinaryArgument(arg, arr, n)); - missNorm = miss && missNorm ? miss.total() / missNorm : 1; - } - let norm = finalResult.total(); - finalResult = op.call(finalResult, arg); - norm = norm ? finalResult.total() / norm : 1; - if (crit) { - const result2 = combineDiceWithNormalization( - crit, - critNorm, - "crit", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (save) { - const result2 = combineDiceWithNormalization( - save, - saveNorm, - "saveHalf", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (miss) { - const result2 = combineDiceWithNormalization( - miss, - missNorm, - "missDamage", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (pc) { - const result2 = combineDiceWithNormalization( - pc, - pcNorm, - "pc", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - op = parseOperation(arr); - } - return finalResult; -} -function parseArgument(s, n) { - let result = parseArgumentInternal(s, n); - while (true) { - const next = parseArgumentInternal(s, n); - if (next === void 0) break; - result = multiplyDiceByDice(result, next); - } - return result; -} -function multiplyDiceByDice(d1, d2) { - if (typeof d1 === "number") d1 = Dice.scalar(d1); - if (typeof d2 === "number") d2 = Dice.scalar(d2); - const result = new Dice(); - const faces = /* @__PURE__ */ new Map(); - let normalizationFactor = 1; - for (const key of d1.keys()) { - let face; - if (typeof key !== "number") { - continue; - } - if (d2.privateData.keep) { - const faceCount = d2.keys().length; - if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { - throw new DiceParseError( - `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` - ); - } - const repeat = Array(key).fill(d2); - face = opDice(repeat, d2.privateData.keep); - } else { - face = multiplyDice(key, d2); - } - normalizationFactor *= face.total(); - faces.set(key, face); - } - for (const [k, face] of faces) { - const count = d1.get(k); - result.combineInPlace( - face.normalize(count * normalizationFactor / face.total()) - ); - } - result.privateData.except = {}; - return result; -} -function multiplyDice(n, d2) { - if (n > MAX_DICE_COUNT) { - throw new DiceParseError( - `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` - ); - } - if (n === 0) return new Dice(0); - if (n === 1) return d2; - const half = Math.floor(n / 2); - let result = multiplyDice(half, d2); - result = result.add(result); - if (n % 2 === 1) { - result = result.add(d2); - } - return result; -} -function opDice(diceList, keepFn) { - return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); -} -function opDiceInternal(diceList, result, index, values, weight, combineFn) { - if (index === diceList.length) { - return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); - } - const currentDice = diceList[index]; - for (const face of currentDice.keys()) { - values.push(face); - result = opDiceInternal( - diceList, - result, - index + 1, - values, - weight * currentDice.get(face), - combineFn - ); - values.pop(); - } - return result; -} -function parseArgumentInternal(s, n) { - if (s.length === 0) return; - const c = s[0]; - switch (c) { - case "(": - s.shift(); - return assertToken(s, ")", parseExpression(s, n)); - case "h": - case "d": - return parseDice(s, n); - case "k": - assertToken(s, "k"); - return parseKeep(s, n); - case "n": - return parseNumber(s, n); - default: - if (isDigit(c)) return parseNumber(s, n); - return; - } -} -function parseBinaryArgument(arg, arr, n) { - if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { - assertToken(arr, "half"); - const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; - return diceArg.divideRoundDown(2); - } - const parsed = parseArgument(arr, n); - return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; -} -function assertToken(s, expected, ret) { - for (const ch of expected) { - const found = s.shift(); - if (found !== ch) { - throw new Error(`Expected character '${ch}', found '${found}'`); - } - } - return ret; -} -function parseDice(s, n) { - let rerollOne = false; - if (peek(s, "hd") && peekIsNumber(s, 2)) { - assertToken(s, "h"); - assertToken(s, "d"); - rerollOne = true; - } else if (peek(s, "d") && peekIsNumber(s, 1)) { - assertToken(s, "d"); - } else { - return; - } - const sides = parseNumber(s, n); - if (sides > MAX_DIE_SIDES) { - throw new DiceParseError( - `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` - ); - } - let result = new Dice(sides); - if (rerollOne) { - result = result.reroll(1); - } - return result; -} -function peek(arr, expected) { - if (expected.length > arr.length) return false; - for (let i = 0; i < expected.length; i++) { - if (arr[i] !== expected.charAt(i)) return false; - } - return true; -} -function peekIsNumber(arr, index) { - if (index >= arr.length) return false; - return isDigit(arr[index]) || arr[index] === "n"; -} -function parseNumber(s, n) { - let ret = ""; - while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { - const ch = s.shift(); - ret += ch === "n" ? n.toString() : ch; - } - if (ret.length === 0) { - throw new Error(`Expected number, found: '${s[0]}'`); - } - return parseInt(ret, 10); -} -function isDigit(c) { - return c >= "0" && c <= "9"; -} -function parseKeep(s, n) { - let keepLowest = false; - if (peek(s, "l")) { - assertToken(s, "l"); - keepLowest = true; - } else if (peek(s, "h")) { - assertToken(s, "h"); - keepLowest = false; - } else { - return; - } - const keepCount = parseNumber(s, n); - const result = parseArgumentInternal(s, n); - if (result instanceof Dice) { - result.privateData.keep = keepN(keepCount, keepLowest); - return result; - } - throw new Error("Expected Dice after keep modifier"); -} -function keepN(n, low) { - return (values) => { - const sorted = [...values].sort((a, b) => low ? a - b : b - a); - return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); - }; -} -function parseOperation(s) { - switch (s[0]) { - case ")": - return; - case "a": - assertToken(s, "ac"); - return Dice.prototype.ac; - case "d": - assertToken(s, "dc"); - return Dice.prototype.dc; - case "!": - assertToken(s, "!"); - const adv = Dice.prototype.advantage; - adv.unary = true; - return adv; - case ">": - assertToken(s, ">"); - return Dice.prototype.max; - case "<": - assertToken(s, "<"); - return Dice.prototype.min; - case "+": - assertToken(s, "+"); - return Dice.prototype.addNonZero; - case "~": - assertToken(s, "~"); - assertToken(s, "+"); - return Dice.prototype.add; - case "-": - assertToken(s, "-"); - return Dice.prototype.subtract; - case "&": - assertToken(s, "&"); - return Dice.prototype.combine; - case "r": - assertToken(s, "reroll"); - return Dice.prototype.reroll; - case "*": - assertToken(s, "*"); - if (peek(s, "*")) { - assertToken(s, "*"); - return Dice.prototype.multiply; - } - return Dice.prototype.conditionalApply; - case "/": - assertToken(s, "/"); - if (s[0] === "/") { - assertToken(s, "/"); - return Dice.prototype.divideRoundDown; - } - return Dice.prototype.divideRoundUp; - case "=": - assertToken(s, "="); - return Dice.prototype.eq; - } - return; -} - -// src/builder/prob.ts -function d20PmfFromCdf(cdfPow, eps = EPS) { - const out = /* @__PURE__ */ new Map(); - let prev = 0; - for (let k = 1; k <= 20; k++) { - const cur = cdfPow(k); - const pk = cur - prev; - if (pk > 0) { - out.set(k, pk); - } - prev = cur; - } - return PMF.fromMap(out, eps); -} - -// src/builder/d20.ts -var cacheKeyMap = { - "flat-flat": "d20", - "flat-reroll": "hd20", - "advantage-flat": "d20 > d20", - "advantage-reroll": "hd20 > hd20", - "disadvantage-flat": "d20 < d20", - "disadvantage-reroll": "hd20 < hd20", - "elven accuracy-flat": "d20 > d20 > d20", - "elven accuracy-reroll": "hd20 > hd20 > hd20" -}; -function d20RollPMF(rollType, rerollOne = false) { - rollType = rollType || "flat"; - const cacheKeyLookup = `${rollType}-${rerollOne ? "reroll" : "flat"}`; - const cacheKey = cacheKeyMap[cacheKeyLookup]; - if (!cacheKey) { - throw new Error(`Invalid roll type: ${rollType}`); - } - const cached = pmfCache.get(cacheKey); - if (cached) return cached; - const base = d20PMF(rerollOne); - if (!rollType || rollType === "flat") { - pmfCache.set(cacheKey, base); - return base; - } - const p = new Array(21).fill(0); - for (const [r, rec] of base) { - const pr = typeof rec === "number" ? rec : rec.p; - if (r >= 1 && r <= 20) p[r] = pr; - } - const F = new Array(21).fill(0); - for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k]; - const eps = 0; - let result = base; - if (rollType === "advantage") { - result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps); - } else if (rollType === "elven accuracy") { - result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps); - } else if (rollType === "disadvantage") { - result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps); - } - pmfCache.set(cacheKey, result); - return result; -} -function d20PMF(rerollOne) { - const cacheKey = `flat-${rerollOne ? "reroll" : "flat"}`; - const cached = pmfCache.get(cacheKey); - if (cached) return cached; - const m = /* @__PURE__ */ new Map(); - const base = 1 / 20; - const rerollShare = base * base; - if (!rerollOne) { - for (let r = 1; r <= 20; r++) { - m.set(r, base); - } - } else { - for (let r = 1; r <= 20; r++) { - m.set(r, (r === 1 ? 0 : base) + rerollShare); - } - } - const result = PMF.fromMap(m, EPS); - pmfCache.set(cacheKey, result); - return result; -} - -// src/builder/roll.ts -var defaultConfig = { - count: 1, - sides: 0, - modifier: 0, - reroll: 0, - explode: 0, - minimum: 0, - bestOf: 0, - keep: void 0, - rollType: "flat" -}; -var rollConfigsEqual = (a, b) => { - return a.count === b.count && a.sides === b.sides && a.modifier === b.modifier && a.reroll === b.reroll && a.explode === b.explode && a.minimum === b.minimum && a.bestOf === b.bestOf && a.keep === b.keep && a.rollType === b.rollType; -}; -var configComplexityScore = (config) => { - return (config.reroll > 0 ? 1 : 0) + (config.explode > 0 ? 1 : 0) + (config.minimum > 0 ? 1 : 0) + (config.bestOf > 0 ? 1 : 0) + (config.keep !== void 0 ? 1 : 0) + (config.rollType !== "flat" ? 1 : 0); -}; -var RollBuilder = class _RollBuilder { - constructor(countOrConfigs = 1) { - // --- Dice Shortcut Methods --- - this.d4 = () => this.d(4); - this.d6 = () => this.d(6); - this.d8 = () => this.d(8); - this.d10 = () => this.d(10); - this.d12 = () => this.d(12); - this.d20 = () => this.d(20); - this.d100 = () => this.d(100); - if (typeof countOrConfigs === "number") { - const count = countOrConfigs; - if (isNaN(count)) throw new Error("Invalid NaN value for count"); - this.subRollConfigs = [ - { ...defaultConfig, count, isSubtraction: count < 0 } - ]; - } else { - this.subRollConfigs = countOrConfigs.map((c) => ({ ...c })); - } - } - create(configs) { - return new _RollBuilder(configs); - } - get lastConfig() { - return this.subRollConfigs[this.subRollConfigs.length - 1]; - } - hasHiddenState() { - return false; - } - getSubRollConfigs() { - return this.subRollConfigs.map((c) => ({ ...c })); - } - // for testing - static fromConfig(config) { - return new _RollBuilder([{ ...defaultConfig, ...config }]); - } - static fromConfigs(configs) { - return new _RollBuilder( - configs.map((config) => ({ ...defaultConfig, ...config })) - ); - } - static fromArgs(...args) { - if (args.length === 1) { - const arg = args[0]; - if (typeof arg === "number") { - if (isNaN(arg)) throw new Error("Invalid NaN value for argument"); - return new _RollBuilder(0).plus(arg); - } - if (typeof arg === "string") { - return new ParsedRollBuilder(arg); - } - if (arg instanceof _RollBuilder) { - return arg; - } - } - if (args.length === 2 || args.length === 3) { - const [count, sidesOrDie, modifier] = args; - if (typeof count !== "number") { - throw new Error("First argument must be a number for multi-arg call"); - } - if (isNaN(count)) throw new Error("Invalid NaN value for count argument"); - if (sidesOrDie instanceof _RollBuilder) { - if (sidesOrDie.hasHiddenState()) { - throw new Error( - "Cannot use a roll with hidden state (like a pooled roll) as a die type." - ); - } - const subRollConfigs = sidesOrDie.getSubRollConfigs(); - if (subRollConfigs.length === 0) { - const result = new _RollBuilder(0); - return modifier !== void 0 ? result.plus(modifier) : result; - } - const absCount = Math.abs(count); - const newConfigs = subRollConfigs.map((config) => ({ - ...config, - count: config.count * absCount, - modifier: config.modifier * absCount - })); - let resultBuilder = new _RollBuilder(newConfigs); - if (count < 0) { - const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); - resultBuilder = new _RollBuilder(negatedConfigs); - } - return modifier !== void 0 ? resultBuilder.plus(modifier) : resultBuilder; - } else if (typeof sidesOrDie === "number" || sidesOrDie === void 0) { - if (typeof sidesOrDie === "number" && isNaN(sidesOrDie)) - throw new Error("Invalid NaN value for sides argument"); - let builder = new _RollBuilder(count); - if (sidesOrDie && sidesOrDie > 0) { - builder = builder.d(sidesOrDie); - } - return modifier !== void 0 ? builder.plus(modifier) : builder; - } - } - throw new Error(`Invalid arguments passed: ${args.join(", ")}`); - } - // --- Core Dice Methods --- - d(sides) { - if (sides !== void 0 && isNaN(sides)) - throw new Error("Invalid NaN value for sides"); - if (sides === void 0) return this; - if (this.lastConfig.sides && this.lastConfig.sides > 0) { - throw new Error("Cannot add a die after adding a die"); - } - if (sides === 0) return this; - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].sides = sides; - return this.create(newConfigs); - } - plus(modOrRoll, die) { - if (typeof modOrRoll === "number" && isNaN(modOrRoll)) - throw new Error("Invalid NaN value for modOrRoll"); - if (die instanceof _RollBuilder && typeof modOrRoll === "number") { - if (die.hasHiddenState()) { - throw new Error( - "Cannot use a roll with hidden state (like a pooled roll) as a die type." - ); - } - const count = modOrRoll; - const subRollConfigs = die.getSubRollConfigs(); - if (subRollConfigs.length === 0) return this; - const absCount = Math.abs(count); - const newConfigs = subRollConfigs.map((config) => ({ - ...config, - count: config.count * absCount, - modifier: config.modifier * absCount - })); - let rollToAdd = new _RollBuilder(newConfigs); - if (count < 0) { - const negatedConfigs = rollToAdd.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); - rollToAdd = new _RollBuilder(negatedConfigs); - } - return this.add(rollToAdd); - } - if (die !== void 0) { - throw new Error("Invalid arguments to plus()"); - } - if (modOrRoll === void 0) return this; - if (typeof modOrRoll === "number") { - if (modOrRoll === 0) return this; - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].modifier += modOrRoll; - return this.create(newConfigs); - } - return this.add(modOrRoll); - } - minus(modOrRoll, die) { - const isNumber = typeof modOrRoll === "number"; - const dieIsRoll = die instanceof _RollBuilder; - if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die); - if (die !== void 0) throw new Error("Invalid arguments to minus()"); - if (modOrRoll === void 0) return this; - return isNumber ? this.plus(-modOrRoll) : this.plus(-1, modOrRoll); - } - /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ - reroll(value) { - if (isNaN(value)) throw new Error("Invalid NaN value for reroll"); - if (value === this.lastConfig.reroll) return this; - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].reroll = value; - return this.create(newConfigs); - } - /** Set finite explode count for max-face explosions (Infinity allowed). */ - explode(count = Infinity) { - if (count !== void 0 && isNaN(count)) - throw new Error("Invalid NaN value for explode count"); - if (count === void 0) return this; - if (count === 0) return this; - if (count < 0) throw new Error("Explode count must be >= 0"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].explode = count; - return this.create(newConfigs); - } - /** Apply per-die minimum value (min > 0). */ - minimum(val) { - if (val !== void 0 && isNaN(val)) - throw new Error("Invalid NaN value for minimum"); - if (val === void 0) return this; - if (val === 0) return this; - if (val < 0) throw new Error("Minimum value must be >= 0"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].minimum = val + 1; - return this.create(newConfigs); - } - bestOf(count) { - if (count !== void 0 && isNaN(count)) - throw new Error("Invalid NaN value for bestOf count"); - if (count === void 0) return this; - if (count <= 0) throw new Error("Best of count must be > 0"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].bestOf = count; - return this.create(newConfigs); - } - keepHighest(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepHighest"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].keep = { total, count, mode: "highest" }; - return this.create(newConfigs); - } - keepLowest(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepLowest"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].keep = { total, count, mode: "lowest" }; - return this.create(newConfigs); - } - keepHighestAll(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepHighestAll"); - const currentAST = this.toAST(); - const trialPool = { - type: "sum", - count: total, - child: currentAST - }; - const keepNode = { - type: "keep", - mode: "highest", - count, - child: trialPool - }; - const currentExpr = this.toExpression(); - const expression = `${total}kh${count}(${currentExpr})`; - return new PooledRollBuilder(keepNode, expression); - } - keepLowestAll(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepLowestAll"); - const currentAST = this.toAST(); - const trialPool = { - type: "sum", - count: total, - child: currentAST - }; - const keepNode = { - type: "keep", - mode: "lowest", - count, - child: trialPool - }; - const currentExpr = this.toExpression(); - const expression = `${total}kl${count}(${currentExpr})`; - return new PooledRollBuilder(keepNode, expression); - } - withAdvantage() { - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].rollType = "advantage"; - return this.create(newConfigs); - } - withDisadvantage() { - const configs = this.getSubRollConfigs(); - configs[configs.length - 1].rollType = "disadvantage"; - return this.create(configs); - } - add(anotherRoll) { - if (anotherRoll === void 0) return this; - if (anotherRoll.hasHiddenState()) { - throw new Error( - "Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll)." - ); - } - const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; - return this.create(configs); - } - withBonus(anotherRoll) { - const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; - return this.create(configs); - } - addRoll(count = 1) { - if (isNaN(count)) throw new Error("Invalid NaN value for count"); - const configs = [ - ...this.subRollConfigs, - { - ...defaultConfig, - count, - isSubtraction: count < 0 - } - ]; - return this.create(configs); - } - scaleDice(scale) { - const scaleInt = Math.floor(scale); - if (scaleInt !== scale) throw new Error("Scale must be an integer"); - if (scaleInt <= 0) throw new Error("Scale must be > 0"); - const newConfigs = this.getSubRollConfigs().map((config) => { - if (!config.sides || config.sides <= 0) return config; - return { ...config, count: config.count * scaleInt }; - }); - return this.create(newConfigs); - } - doubleDice() { - return this.scaleDice(2); - } - alwaysHits() { - return new AlwaysHitBuilder(this); - } - alwaysCrits() { - return new AlwaysCritBuilder(this); - } - copy() { - return this.create(this.getSubRollConfigs()); - } - withElvenAccuracy() { - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].rollType = "elven accuracy"; - return this.create(newConfigs); - } - toExpression() { - const originalDiceConfigs = this.subRollConfigs.filter( - (config) => config.sides && config.sides > 0 - ); - const configGroups = /* @__PURE__ */ new Map(); - for (const config of originalDiceConfigs) { - const keyConfig = { ...config }; - delete keyConfig.count; - delete keyConfig.modifier; - const key = JSON.stringify(keyConfig); - const existingGroup = configGroups.get(key); - if (existingGroup) { - existingGroup.totalCount += config.count; - } else { - configGroups.set(key, { config, totalCount: config.count }); - } - } - const rootConfig = this.getRootDieConfig(); - const groupedConfigs = Array.from(configGroups.values()); - let rootD20Group; - if (rootConfig && rootConfig.sides === 20) { - const rootIndex = groupedConfigs.findIndex( - ({ config }) => rollConfigsEqual(config, rootConfig) && JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep) - ); - if (rootIndex !== -1) { - rootD20Group = groupedConfigs.splice(rootIndex, 1)[0]; - } - } - const sortedDiceConfigs = groupedConfigs.map(({ config, totalCount }) => ({ - ...config, - count: totalCount - })).sort((a, b) => { - const aHasPriority = a.reroll > 0 || a.minimum > 0; - const bHasPriority = b.reroll > 0 || b.minimum > 0; - if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1; - if (b.sides !== a.sides) return b.sides - a.sides; - return configComplexityScore(b) - configComplexityScore(a); - }); - const diceConfigs = rootD20Group ? [ - { ...rootD20Group.config, count: rootD20Group.totalCount }, - ...sortedDiceConfigs - ] : sortedDiceConfigs; - const totalModifier = this.subRollConfigs.reduce( - (sum, config) => sum + config.modifier, - 0 - ); - if (diceConfigs.length === 0) return totalModifier.toString(); - const rootDieConfig = this.getRootDieConfig(); - const newRootConfig = rootDieConfig ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig)) : void 0; - const diceExpressions = diceConfigs.map( - (config) => this.configToSingleExpressionWithoutModifier( - config, - config === newRootConfig - ) - ); - let result = ""; - for (let i = 0; i < diceExpressions.length; i++) { - const config = diceConfigs[i]; - const expression = diceExpressions[i]; - if (i === 0) { - result = (config.isSubtraction ? "-" : "") + expression; - if (config.sides === 20 && totalModifier !== 0) { - if (totalModifier > 0) result += ` + ${totalModifier}`; - else result += ` - ${Math.abs(totalModifier)}`; - } - } else { - const operator = config.isSubtraction ? " - " : " + "; - result += operator + expression; - } - } - if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) { - if (totalModifier > 0) result += ` + ${totalModifier}`; - else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`; - } - return result.replace(/\+ -/g, "-"); - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - get pmf() { - return this.toPMF(); - } - toQuery(eps = 0) { - return this.toPMF(eps).query(); - } - toAST() { - const configs = this.getSubRollConfigs(); - return astFromRollConfigs(configs) || { type: "constant", value: 0 }; - } - configToSingleExpressionWithoutModifier(config, isRootDie) { - if (!config.sides || config.sides <= 0) return ""; - let baseDie = `d${config.sides}`; - if (config.reroll > 0) { - if (config.minimum > 0 && config.explode > 0) ; else if (config.minimum > 0) { - for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`; - } else { - for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`; - } - } - if (config.minimum > 0) { - if (config.reroll > 0 && !config.explode) { - baseDie = `${config.minimum}>(${baseDie})`; - } else { - baseDie = `${config.minimum}>${baseDie}`; - } - if (config.reroll > 0 && config.explode > 0) { - for (let i = 1; i <= config.reroll; i++) { - baseDie += ` reroll ${i}`; - } - } - } - if (baseDie === "d20 reroll 1" && config.minimum <= 1) baseDie = "hd20"; - let mainExpression = ""; - switch (config.rollType) { - case "advantage": - mainExpression = `${baseDie} > ${baseDie}`; - break; - case "disadvantage": - mainExpression = `${baseDie} < ${baseDie}`; - break; - case "elven accuracy": - mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`; - break; - case "flat": - if (config.keep) { - const mode = config.keep.mode === "highest" ? "kh" : "kl"; - const baseDieExpression = this.configToSingleExpressionWithoutModifier( - { - ...config, - count: config.count, - modifier: 0, - rollType: "flat", - keep: void 0 - }, - false - ); - mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`; - } else { - const isComplex = baseDie.length > `d${config.sides}`.length; - const isHalflingShorthand = baseDie === "hd20"; - const isD20Shorthand = baseDie === "d20" && isRootDie; - const hasMinimum = config.minimum > 0; - const hasReroll = config.reroll > 0; - const effectiveCount = config.isSubtraction ? Math.abs(config.count) : config.count < 0 ? 1 : Math.abs(config.count); - if (effectiveCount > 1) { - const shouldAddParentheses = isComplex; - mainExpression = shouldAddParentheses ? `${effectiveCount}(${baseDie})` : `${effectiveCount}${baseDie}`; - } else if (effectiveCount === 1) { - const needsParens = hasReroll && hasMinimum; - if (config.isSubtraction) { - mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; - } else if (isComplex || isHalflingShorthand || isD20Shorthand || config.count < 0) { - mainExpression = needsParens ? `1(${baseDie})` : baseDie; - } else { - mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; - } - } else { - mainExpression = baseDie; - } - } - if (config.bestOf && config.count && config.bestOf < config.count) { - mainExpression += `kh${config.bestOf}`; - } - break; - } - return mainExpression; - } - getRootDieConfig() { - const configs = this.subRollConfigs; - return configs.find((config) => config.sides > 0) || configs[0]; - } - getAllDieConfigs() { - return this.getSubRollConfigs(); - } - getBonusDiceConfigs() { - const allConfigs = this.subRollConfigs; - const rootConfig = allConfigs.find((config) => config.sides > 0) || allConfigs[0]; - if (!rootConfig) return []; - return allConfigs.filter((config) => config.sides > 0).filter((config) => config !== rootConfig); - } - getBonusDicePMFs(check, eps = 0) { - return check.getBonusDiceConfigs().map( - (config) => pmfFromRollBuilder(_RollBuilder.fromConfigs([config]), eps) - ); - } - get modifier() { - return this.subRollConfigs.reduce( - (sum, config) => sum + config.modifier, - 0 - ); - } - get rollType() { - const rootConfig = this.getRootDieConfig(); - return rootConfig?.rollType || "flat"; - } - get baseReroll() { - const rootConfig = this.getRootDieConfig(); - return rootConfig?.reroll || 0; - } - half() { - return new HalfRollBuilder(this); - } - /** - * Scale this roll's result by `numerator / denominator`, rounding each outcome. - * A general, composable form of {@link half} — used to model damage-type resistance - * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). - * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. - */ - scaleResult(numerator, denominator = 1, rounding = "floor") { - return new ScaleRollBuilder(this, numerator, denominator, rounding); - } - // Create a "max of N rolls" version of this roll for crit damage with keep operations - maxOf(count) { - return new MaxOfRollBuilder(this, count); - } - // These methods are implemented via prototype augmentation in ac.ts and dc.ts - // They are declared here to provide proper TypeScript types - ac(_targetAC) { - throw new Error("ac() should be implemented via prototype augmentation"); - } - dc(_saveDC) { - throw new Error("dc() should be implemented via prototype augmentation"); - } -}; -var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder { - constructor(innerRoll) { - super(0); - this.innerRoll = innerRoll; - } - hasHiddenState() { - return this.innerRoll.hasHiddenState(); - } - // No need to override create if we don't expose RollBuilder methods that use it, - // but HalfRollBuilder extends RollBuilder so it does. - // However, HalfRollBuilder seems to just wrap another roll. - // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder? - // No, RollBuilder.plus returns RollBuilder. - // The inheritance here is a bit tricky. - // Existing code for HalfRollBuilder doesn't seem to implement plus/etc. - // So .plus() on a HalfRollBuilder would return a RollBuilder (base class). - // Which is fine. - // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that. - get lastConfig() { - return this.innerRoll.lastConfig; - } - getSubRollConfigs() { - return this.innerRoll.getSubRollConfigs(); - } - toExpression() { - const innerExpression = this.innerRoll.toExpression(); - return `(${innerExpression}) // 2`; - } - toAST() { - return { - type: "half", - child: this.innerRoll.toAST() - }; - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _HalfRollBuilder(this.innerRoll.copy()); - } -}; -var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder { - constructor(innerRoll, numerator, denominator = 1, rounding = "floor") { - super(0); - this.innerRoll = innerRoll; - this.numerator = numerator; - this.denominator = denominator; - this.rounding = rounding; - } - hasHiddenState() { - return this.innerRoll.hasHiddenState(); - } - get lastConfig() { - return this.innerRoll.lastConfig; - } - getSubRollConfigs() { - return this.innerRoll.getSubRollConfigs(); - } - toExpression() { - const inner = this.innerRoll.toExpression(); - if (this.denominator === 1) return `${this.numerator} * (${inner})`; - if (this.numerator === 1) return `(${inner}) // ${this.denominator}`; - return `(${inner}) * ${this.numerator} // ${this.denominator}`; - } - toAST() { - return { - type: "scale", - numerator: this.numerator, - denominator: this.denominator, - rounding: this.rounding, - child: this.innerRoll.toAST() - }; - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _ScaleRollBuilder( - this.innerRoll.copy(), - this.numerator, - this.denominator, - this.rounding - ); - } -}; -var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder { - constructor(innerRoll, count, diceCount, diceSides) { - super(0); - this.innerRoll = innerRoll; - this.count = count; - this.diceCount = diceCount; - this.diceSides = diceSides; - } - hasHiddenState() { - return this.innerRoll.hasHiddenState(); - } - get lastConfig() { - return this.innerRoll.lastConfig; - } - getSubRollConfigs() { - return this.innerRoll.getSubRollConfigs(); - } - toExpression() { - if (this.diceCount && this.diceSides) { - return `max${this.count}(${this.diceCount}d${this.diceSides})`; - } - return `max${this.count}(?d?)`; - } - toAST() { - if (this.diceCount && this.diceSides) { - const sumChild = { - type: "sum", - count: this.diceCount, - child: { type: "die", sides: this.diceSides } - }; - return { - type: "maxOf", - count: this.count, - child: sumChild - }; - } - try { - const configs = this.innerRoll.getSubRollConfigs(); - if (configs.length === 1 && configs[0].sides) { - const config = configs[0]; - const sumChild = { - type: "sum", - count: config.count, - child: { type: "die", sides: config.sides } - }; - return { - type: "maxOf", - count: this.count, - child: sumChild - }; - } - } catch { - } - throw new Error( - `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration` - ); - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count); - } -}; -var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder { - constructor(baseRoll, attackConfig) { - if (baseRoll.hasHiddenState()) { - throw new Error( - "Cannot create AlwaysHitBuilder from a roll with hidden state." - ); - } - super(baseRoll.getSubRollConfigs()); - if (attackConfig) { - this.attackConfig = { ...attackConfig }; - } else { - this.attackConfig = { critThreshold: 20 }; - } - } - create(configs) { - return new RollBuilder(configs); - } - onHit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new AttackBuilder(this, damageRoll); - } - get critThreshold() { - return this.attackConfig.critThreshold; - } - // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? - critOn(critThreshold) { - const newConfig = { critThreshold }; - return new _AlwaysHitBuilder(this, newConfig); - } - alwaysCrits() { - return new AlwaysCritBuilder(this, void 0, true); - } - // Legacy expressions - toExpression() { - const configs = this.getSubRollConfigs(); - return new RollBuilder(configs).toExpression(); - } - toPMF() { - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - return d20RollPMF(rollType, rerollOne); - } - copy() { - const baseCopy = new RollBuilder(this.getSubRollConfigs()); - const critThreshold = this.critThreshold; - const newConfig = { critThreshold }; - return new _AlwaysHitBuilder(baseCopy, newConfig); - } -}; -var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder { - constructor(baseRoll, attackConfig, fromAlwaysHit = false) { - if (baseRoll.hasHiddenState()) { - throw new Error( - "Cannot create AlwaysCritBuilder from a roll with hidden state." - ); - } - super(baseRoll.getSubRollConfigs()); - if (attackConfig) { - this.attackConfig = { ...attackConfig }; - } else { - this.attackConfig = { critThreshold: 20 }; - } - this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder; - } - create(configs) { - return new RollBuilder(configs); - } - onHit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new AttackBuilder(this, damageRoll); - } - get critThreshold() { - return this.attackConfig.critThreshold; - } - critOn(critThreshold) { - const newConfig = { critThreshold, ac: this.attackConfig.ac }; - return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit); - } - // Legacy expressions - toExpression() { - const configs = this.getSubRollConfigs(); - return new RollBuilder(configs).toExpression(); - } - toPMF() { - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - return d20RollPMF(rollType, rerollOne); - } - copy() { - const baseCopy = new RollBuilder(this.getSubRollConfigs()); - const critThreshold = this.critThreshold; - const newConfig = { critThreshold, ac: this.attackConfig.ac }; - return new _AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit); - } -}; -var ParsedRollBuilder = class _ParsedRollBuilder extends RollBuilder { - constructor(expression) { - super([]); - this.originalExpression = expression; - this.cachedPMF = parse(expression, 0); - } - hasHiddenState() { - return true; - } - create(configs) { - return new RollBuilder(configs); - } - toPMF(_eps = 0) { - return this.cachedPMF; - } - toExpression() { - return this.originalExpression; - } - toAST() { - throw new Error( - "ParsedRollBuilder does not support AST conversion. Use the builder API instead." - ); - } - copy() { - return new _ParsedRollBuilder(this.originalExpression); - } - doubleDice() { - throw new Error( - "ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead." - ); - } -}; -var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder { - constructor(baseAST, baseExpression, configs = []) { - super(configs.length > 0 ? configs : 0); - this.baseAST = baseAST; - this.baseExpression = baseExpression; - } - create(configs) { - return new _PooledRollBuilder(this.baseAST, this.baseExpression, configs); - } - hasHiddenState() { - return true; - } - d(_sides) { - throw new Error("Cannot add dice to a pooled roll. The pool is finalized."); - } - reroll(_value) { - throw new Error("Cannot set reroll on a pooled roll."); - } - explode(_count = Infinity) { - throw new Error("Cannot set explode on a pooled roll."); - } - minimum(_val) { - throw new Error("Cannot set minimum on a pooled roll."); - } - bestOf(_count) { - throw new Error("Cannot set bestOf on a pooled roll."); - } - keepHighest(_total, _count) { - throw new Error( - "Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling." - ); - } - keepLowest(_total, _count) { - throw new Error( - "Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling." - ); - } - withAdvantage() { - throw new Error("Cannot set advantage on a pooled roll."); - } - withDisadvantage() { - throw new Error("Cannot set disadvantage on a pooled roll."); - } - withElvenAccuracy() { - throw new Error("Cannot set elven accuracy on a pooled roll."); - } - toAST() { - const configsAST = super.toAST(); - const isZero = configsAST.type === "constant" && configsAST.value === 0; - if (isZero) { - return this.baseAST; - } - const children = [ - { node: this.baseAST, sign: 1 }, - { node: configsAST, sign: 1 } - ]; - return { type: "add", children }; - } - toExpression() { - const configsExpression = super.toExpression(); - if (configsExpression === "0") { - return this.baseExpression; - } - if (configsExpression.startsWith("-")) { - return `${this.baseExpression} - ${configsExpression.substring(1)}`; - } - return `${this.baseExpression} + ${configsExpression}`; - } - copy() { - return new _PooledRollBuilder( - this.baseAST, - this.baseExpression, - this.getSubRollConfigs() - ); - } - scaleDice(scale) { - const scaleInt = Math.floor(scale); - if (scaleInt !== scale) throw new Error("Scale must be an integer"); - if (scaleInt <= 0) throw new Error("Scale must be > 0"); - const newBaseAST = { - type: "sum", - count: scaleInt, - child: this.baseAST - }; - const newBaseExpr = scaleInt === 1 ? this.baseExpression : `${scaleInt}(${this.baseExpression})`; - return new _PooledRollBuilder( - newBaseAST, - newBaseExpr, - this.getSubRollConfigs() - ); - } - times(count) { - if (isNaN(count)) throw new Error("Invalid NaN value for times"); - if (Math.floor(count) !== count) - throw new Error("times() requires an integer"); - if (count < 0) throw new Error("times() requires a non-negative integer"); - const currentAST = this.toAST(); - const currentExpr = this.toExpression(); - const sumNode = { - type: "sum", - count, - child: currentAST - }; - const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`; - return new _PooledRollBuilder(sumNode, newExpr); - } -}; -var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder { - constructor(parts) { - super(0); - this.parts = parts; - } - hasHiddenState() { - return true; - } - getSubRollConfigs() { - return []; - } - toAST() { - return { - type: "add", - children: this.parts.map((p) => ({ - node: p.toAST(), - sign: 1 - })) - }; - } - toExpression() { - const exprs = this.parts.map((p) => p.toExpression()).filter((e) => e && e !== "0"); - if (exprs.length === 0) return "0"; - let result = exprs[0]; - for (let i = 1; i < exprs.length; i++) { - const e = exprs[i]; - result += e.startsWith("-") ? ` - ${e.substring(1)}` : ` + ${e}`; - } - return result.replace(/\+ -/g, "-"); - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _CompositeSumRollBuilder(this.parts.map((p) => p.copy())); - } -}; -function sumRolls(parts) { - const meaningful = parts.filter((p) => p !== void 0); - if (meaningful.length === 0) return new RollBuilder(0); - if (meaningful.length === 1) return meaningful[0]; - return new CompositeSumRollBuilder(meaningful); -} - -// src/builder/factory.ts -var rollFn = (count, sidesOrDie, modifier) => { - if (sidesOrDie instanceof RollBuilder) { - if (sidesOrDie.hasHiddenState()) { - throw new Error( - "Cannot use a roll with hidden state (like a pooled roll) as a die type." - ); - } - const subRollConfigs = sidesOrDie.getSubRollConfigs(); - if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier); - const absCount = Math.abs(count); - const newConfigs = subRollConfigs.map((config) => ({ - ...config, - count: config.count * absCount, - modifier: config.modifier * absCount - })); - let resultBuilder = new RollBuilder(newConfigs); - if (count < 0) { - const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); - resultBuilder = new RollBuilder(negatedConfigs); - } - return resultBuilder.plus(modifier); - } else { - let builder = new RollBuilder(count); - if (sidesOrDie && sidesOrDie > 0) { - builder = builder.d(sidesOrDie); - } - return builder.plus(modifier); - } -}; -rollFn.d = (sides) => { - if (typeof sides === "string") { - return RollBuilder.fromArgs(sides); - } - return new RollBuilder(1).d(sides); -}; -rollFn.hd20 = () => new RollBuilder(1).d20().reroll(1); -rollFn.d4 = () => new RollBuilder(1).d4(); -rollFn.d6 = () => new RollBuilder(1).d6(); -rollFn.d8 = () => new RollBuilder(1).d8(); -rollFn.d10 = () => new RollBuilder(1).d10(); -rollFn.d12 = () => new RollBuilder(1).d12(); -rollFn.d20 = () => new RollBuilder(1).d20(); -rollFn.d100 = () => new RollBuilder(1).d100(); -rollFn.flat = (n) => new RollBuilder(0).plus(n); -function d(sides) { - if (typeof sides === "string") { - return RollBuilder.fromArgs(sides); - } - return new RollBuilder(1).d(sides); -} -var d4 = new RollBuilder(1).d4(); -var d6 = new RollBuilder(1).d6(); -var d8 = new RollBuilder(1).d8(); -var d10 = new RollBuilder(1).d10(); -var d12 = new RollBuilder(1).d12(); -var d20 = new RollBuilder(1).d20(); -var hd20 = new RollBuilder(1).d20().reroll(1); -var d100 = new RollBuilder(1).d100(); -var flat = (n) => new RollBuilder(0).plus(n); -var roll = rollFn; -var builderPMFCache = new LRUCache(1e3); - -// src/builder/ast.ts -var defaultEps = 0; -var singleDiePMFCache = new LRUCache(1e3); -function astFromRollConfigs(configs) { - if (!configs || configs.length === 0) return void 0; - const children = []; - let constantSum = 0; - for (const cfg of configs) { - const sign = cfg.isSubtraction || cfg.count < 0 ? -1 : 1; - const count = Math.abs(cfg.count || 0); - constantSum += cfg.modifier || 0; - if ((cfg.sides || 0) <= 0) continue; - const die = { - type: "die", - sides: cfg.sides, - reroll: cfg.reroll > 0 ? cfg.reroll : void 0, - minimum: cfg.minimum > 0 ? cfg.minimum : void 0, - explode: cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0 ? cfg.explode : void 0 - }; - let node = die; - let appliedRollType = false; - if (cfg.rollType && cfg.rollType !== "flat") { - if (cfg.sides === 20) { - node = { - type: "d20Roll", - rollType: cfg.rollType, - child: node - }; - } else { - const n = cfg.rollType === "elven accuracy" ? 3 : 2; - const mode = cfg.rollType === "disadvantage" ? "lowest" : "highest"; - const base = { type: "sum", count: n, child: node }; - node = { type: "keep", mode, count: 1, child: base }; - } - appliedRollType = true; - } - if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) { - const baseCount = Math.max(1, Math.floor(Math.abs(count || 1))); - const trials = Math.max(1, Math.floor(cfg.keep.total)); - const k = Math.max(0, Math.floor(cfg.keep.count)); - if (k === 1 && cfg.keep.mode === "highest") { - const perTrial = { - type: "sum", - count: baseCount, - child: node - }; - if (trials === 1) { - node = perTrial; - } else { - node = { - type: "maxOf", - count: trials, - child: perTrial - }; - } - } else if (trials === baseCount) { - const base = { type: "sum", count: trials, child: node }; - node = { - type: "keep", - mode: cfg.keep.mode, - count: k, - child: base - }; - } else { - const perTrial = { - type: "sum", - count: baseCount, - child: node - }; - if (trials === 1) { - node = perTrial; - } else { - const trialPool = { - type: "sum", - count: trials, - child: perTrial - }; - node = { - type: "keep", - mode: cfg.keep.mode, - count: k, - child: trialPool - }; - } - } - } else { - const c = appliedRollType ? 1 : Math.max(1, count || 1); - node = { type: "sum", count: c, child: node }; - } - children.push({ node, sign }); - } - if (children.length === 0) { - return { type: "constant", value: constantSum }; - } - const add = { type: "add", children }; - if (constantSum !== 0) - add.children.push({ - node: { type: "constant", value: constantSum }, - sign: 1 - }); - return add; -} -function resolve(node, eps = defaultEps) { - const signature = getASTSignature(node); - const cacheKey = `${signature}_${eps}`; - const cached = builderPMFCache.get(cacheKey); - if (cached) return cached; - const result = (() => { - switch (node.type) { - case "constant": - return PMF.delta(node.value, eps); - case "die": { - return resolveSingleDie(node, eps); - } - case "sum": { - const base = resolve(node.child, eps); - const n = Math.max(0, Math.floor(node.count)); - if (n === 0) return PMF.delta(0, eps); - if (n === 1) return base; - return base.power(n, eps); - } - case "add": { - let shift = 0; - const parts = []; - for (const c of node.children) { - if (c.node.type === "constant") { - shift += c.sign * c.node.value; - } else { - const p = resolve(c.node, eps); - parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v)); - } - } - if (parts.length === 0) return PMF.delta(shift, eps); - let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps); - if (shift !== 0) res = res.mapDamage((v) => v + shift); - return res; - } - case "keep": { - const totalTrials = getTotalCount(node); - const keepCount = Math.max(0, Math.min(node.count, totalTrials)); - if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps); - const perTrialNode = node.child.child; - const perTrialPMF = resolve(perTrialNode, eps); - return keepSumPMF( - perTrialPMF, - totalTrials, - keepCount, - node.mode === "highest", - eps - ); - } - case "d20Roll": { - const childDie = findDie(node.child); - const rerollOne = !!childDie && (childDie.reroll || 0) >= 1; - return d20RollPMF(node.rollType, rerollOne); - } - case "half": { - const childPMF = resolve(node.child, eps); - return childPMF.scaleDamage(0.5, "floor"); - } - case "maxOf": { - const childPMF = resolve(node.child, eps); - const count = Math.max(1, Math.floor(node.count)); - if (count === 1) return childPMF; - return computeMaxOfPMF(childPMF, count, eps); - } - case "scale": { - const childPMF = resolve(node.child, eps); - const denom = node.denominator === 0 ? 1 : node.denominator; - return childPMF.scaleDamage(node.numerator / denom, node.rounding); - } - } - })(); - builderPMFCache.set(cacheKey, result); - return result; -} -function pmfFromRollBuilder(rb, eps = defaultEps) { - const ast = rb.toAST(); - return resolve(ast, eps); -} -function resolveSingleDie(die, eps = defaultEps) { - const signature = getASTSignature(die); - const cacheKey = `${signature}_${eps}`; - const cached = singleDiePMFCache.get(cacheKey); - if (cached) return cached; - const s = Math.max(0, Math.floor(die.sides)); - if (s <= 0) return PMF.delta(0, eps); - let probs = /* @__PURE__ */ new Map(); - for (let v = 1; v <= s; v++) probs.set(v, 1 / s); - const r = Math.max(0, Math.floor(die.reroll || 0)); - if (r > 0) { - const k = Math.min(r, s); - const rerollMass = k / s; - const uniformReroll = rerollMass / s; - const next = /* @__PURE__ */ new Map(); - for (let v = 1; v <= s; v++) { - const keep = v <= k ? 0 : 1 / s; - next.set(v, keep + uniformReroll); - } - probs = next; - } - let pmf = PMF.fromMap(new Map(probs), eps); - const minV = Math.max(0, Math.floor(die.minimum || 0)); - if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV)); - const explode = die.explode; - if (explode && Number.isFinite(explode) && explode > 0) { - const times = Math.floor(explode); - const maxFace = s; - const nonMax = /* @__PURE__ */ new Map(); - const pMax = pmf.pAt(maxFace); - for (const v of pmf.support()) { - if (v !== maxFace) nonMax.set(v, pmf.pAt(v)); - } - let nonMaxPMF = PMF.fromMap(nonMax, eps); - if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) { - nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax); - } - let tail = PMF.delta(0, eps); - const addOnce = pmf; - for (let t = 1; t <= times; t++) { - tail = tail.convolve(addOnce, eps); - } - const exploded = PMF.branch( - tail.mapDamage((v) => v + maxFace), - nonMaxPMF, - pMax - ); - pmf = exploded; - } - singleDiePMFCache.set(cacheKey, pmf); - return pmf; -} -function findDie(node) { - switch (node.type) { - case "die": - return node; - case "constant": - return void 0; - case "sum": - case "d20Roll": - case "half": - case "maxOf": - case "scale": - return findDie(node.child); - case "keep": - return findDie(node.child.child); - case "add": - for (const c of node.children) { - const d2 = findDie(c.node); - if (d2) return d2; - } - return void 0; - } -} -function getTotalCount(node) { - let cur = node.child; - while (cur.type === "keep") cur = cur.child; - return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0; -} -function computeMaxOfPMF(pmf, count, eps = defaultEps) { - if (count <= 1) return pmf; - const support = pmf.support(); - const out = /* @__PURE__ */ new Map(); - if (count <= 6 && support.length <= 20) { - let dfs2 = function(rollsLeft, currentMax, probability) { - if (rollsLeft === 0) { - out.set(currentMax, (out.get(currentMax) || 0) + probability); - return; - } - for (const value of support) { - const p = pmf.pAt(value); - if (p > 0) { - const newMax = Math.max(currentMax, value); - dfs2(rollsLeft - 1, newMax, probability * p); - } - } - }; - dfs2(count, -Infinity, 1); - } else { - const sortedSupport = [...support].sort((a, b) => a - b); - let runningCdf = 0; - for (const value of sortedSupport) { - const prevCdf = runningCdf; - runningCdf += pmf.pAt(value); - const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count); - if (probMax > eps) { - out.set(value, probMax); - } - } - } - return PMF.fromMap(out, eps); -} -function keepSumPMF(single, total, keep, highest, eps = defaultEps) { - if (keep >= total) return single.power(total, eps); - if (keep <= 0) return PMF.delta(0, eps); - const sortedSupport = [...single.support()].sort((a, b) => a - b); - const pmfSig = sortedSupport.map((val) => `${val}:${single.pAt(val).toPrecision(6)}`).join(","); - const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`; - const cached = builderPMFCache.get(cacheKey); - if (cached) return cached; - if (keep === 1) { - if (highest) { - return computeMaxOfPMF(single, total, eps); - } else { - const neg = single.mapDamage((v) => -v); - const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v); - builderPMFCache.set(cacheKey, minPMF); - return minPMF; - } - } - let state = /* @__PURE__ */ new Map(); - const stride = total + 1; - const keyOf = (used, r) => used * stride + r; - state.set(keyOf(0, total), /* @__PURE__ */ new Map([[0, 1]])); - const valuesDesc = highest ? [...sortedSupport].sort((a, b) => b - a) : [...sortedSupport].sort((a, b) => a - b); - const binomPMF = (r, p) => { - if (r <= 0) return [1]; - if (p <= eps) { - const arr2 = new Array(r + 1).fill(0); - arr2[0] = 1; - return arr2; - } - if (1 - p <= eps) { - const arr2 = new Array(r + 1).fill(0); - arr2[r] = 1; - return arr2; - } - const q = 1 - p; - const arr = new Array(r + 1).fill(0); - arr[0] = Math.pow(q, r); - const ratio = p / q; - for (let x = 1; x <= r; x++) - arr[x] = arr[x - 1] * (r - x + 1) / x * ratio; - let s = 0; - for (let x = 0; x <= r; x++) s += arr[x]; - if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s; - return arr; - }; - const pruneMap = (m, threshold) => { - if (threshold <= 0) return m; - const out = /* @__PURE__ */ new Map(); - for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr); - return out.size === m.size ? m : out; - }; - const pruneState = (st, threshold) => { - if (threshold <= 0) return st; - const out = /* @__PURE__ */ new Map(); - for (const [k, m] of st) { - const mm = pruneMap(m, threshold); - if (mm.size > 0) out.set(k, mm); - } - return out; - }; - let processedMass = 0; - for (const v of valuesDesc) { - const p = single.pAt(v); - if (p <= 0) continue; - const q = Math.max(eps, 1 - processedMass); - const pCond = Math.min(1, p / q); - const next = /* @__PURE__ */ new Map(); - for (const [k, m] of state) { - const used = Math.floor(k / stride); - const r = k - used * stride; - if (r === 0) { - const destKey = keyOf(used, 0); - const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); - for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr); - next.set(destKey, dest); - continue; - } - const bin = binomPMF(r, pCond); - const remainingCapacity = keep - used; - for (let x = 0; x <= r; x++) { - const px = bin[x]; - if (px <= eps) continue; - const t = Math.min(x, remainingCapacity); - const used2 = used + t; - const r2 = r - x; - const add = t * v; - const destKey = keyOf(used2, r2); - const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); - for (const [sum, pr] of m) { - const s2 = sum + add; - const prob = pr * px; - const cur = dest.get(s2) || 0; - const nv = cur + prob; - if (nv >= eps) dest.set(s2, nv); - } - if (dest.size > 0) next.set(destKey, dest); - } - } - state = pruneState(next, eps * 1e-6); - processedMass += p; - } - const finalKey = keyOf(keep, 0); - const dist = state.get(finalKey) ?? /* @__PURE__ */ new Map(); - if (dist.size === 0) { - return PMF.emptyMass(); - } - const result = PMF.fromMap(dist, eps); - builderPMFCache.set(cacheKey, result); - return result; -} -function getASTSignature(node) { - switch (node.type) { - case "constant": - return `c:${node.value}`; - case "die": { - const parts = []; - parts.push(`s:${node.sides}`); - if (node.reroll) parts.push(`r:${node.reroll}`); - if (node.minimum) parts.push(`m:${node.minimum}`); - if (node.explode) parts.push(`e:${node.explode}`); - return `d{${parts.join(",")}}`; - } - case "sum": - return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`; - case "d20Roll": - return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`; - case "keep": - return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature( - node.child - )}}`; - case "half": - return `half{ch:${getASTSignature(node.child)}}`; - case "maxOf": - return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`; - case "scale": - return `scale{n:${node.numerator},d:${node.denominator},r:${node.rounding},ch:${getASTSignature(node.child)}}`; - case "add": { - let constantValue = 0; - const otherChildrenSigs = []; - for (const c of node.children) { - if (c.node.type === "constant") { - constantValue += c.sign * c.node.value; - } else { - otherChildrenSigs.push( - `${c.sign === -1 ? "-" : "+"}${getASTSignature(c.node)}` - ); - } - } - if (constantValue !== 0) { - otherChildrenSigs.push( - constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}` - ); - } - otherChildrenSigs.sort(); - return `add[${otherChildrenSigs.join("")}]`; - } - } -} - -// src/builder/attack.ts -var AttackBuilder = class _AttackBuilder { - constructor(check, hitEffect, critEffect, missEffect) { - this.check = check; - this.hitEffect = hitEffect; - this.critEffect = critEffect; - this.missEffect = missEffect; - } - onCrit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new _AttackBuilder( - this.check, - this.hitEffect, - damageRoll, - this.missEffect - ); - } - onMiss(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new _AttackBuilder( - this.check, - this.hitEffect, - this.critEffect, - damageRoll - ); - } - noCrit() { - return new _AttackBuilder(this.check, this.hitEffect, null, this.missEffect); - } - // Legacy expressions - toExpression() { - const checkPart = this.check.toExpression(); - let effectPart = ""; - if (this.hitEffect) { - effectPart = `(${this.hitEffect.toExpression()})`; - if (this.critEffect !== null) { - let crit; - if (this.critEffect) { - crit = this.critEffect; - } else { - if (this.hitEffect instanceof ParsedRollBuilder) { - crit = RollBuilder.fromArgs(0); - } else { - crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0); - } - } - const critThreshold = this.check.critThreshold; - if (critThreshold < 1 || critThreshold > 20) { - throw new Error( - `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.` - ); - } - const critExpression = crit.toExpression(); - if (critExpression !== "0") { - if (critThreshold === 20) { - effectPart += ` crit (${critExpression})`; - } else { - const xcritNumber = 21 - critThreshold; - effectPart += ` xcrit${xcritNumber} (${critExpression})`; - } - } - } - if (this.missEffect) { - effectPart += ` miss (${this.missEffect.toExpression()})`; - } - } - return `${checkPart} * ${effectPart}`; - } - resolveProbabilities(check, eps = 0) { - const rollType = check.rollType; - const rerollOne = check.baseReroll > 0; - const critThreshold = check.critThreshold; - const d202 = d20RollPMF(rollType, rerollOne); - if (check instanceof AlwaysCritBuilder) { - if (check.fromAlwaysHit) { - return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 }; - } - const ac2 = check.attackConfig.ac ?? 0; - const staticMod2 = this.check.modifier; - const bonusDicePMFs2 = this.check.getBonusDicePMFs(this.check, eps); - const bonusPMF2 = bonusDicePMFs2.length ? PMF.convolveMany(bonusDicePMFs2, eps) : PMF.delta(0, eps); - let pcrit2 = 0; - let pmiss2 = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - if (r === 1) { - pmiss2 += pr; - continue; - } - const need = ac2 - staticMod2 - r; - const pBonusHit = bonusPMF2.tailProbGE(need); - pcrit2 += pr * pBonusHit; - pmiss2 += pr * (1 - pBonusHit); - } - return { pSuccess: pcrit2, pHit: 0, pCrit: pcrit2, pMiss: pmiss2 }; - } - if (check instanceof AlwaysHitBuilder) { - let pCrit = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - if (r >= critThreshold) pCrit += pr; - } - const pHit = 1 - pCrit; - const pMiss = 0; - return { pSuccess: 1, pHit, pCrit, pMiss }; - } - const ac = check.attackConfig.ac; - const staticMod = this.check.modifier; - const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps); - const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); - let pcrit = 0; - let phit = 0; - let pmiss = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - if (r === 1) { - pmiss += pr; - continue; - } - if (r >= critThreshold) { - pcrit += pr; - continue; - } - const need = ac - staticMod - r; - const pBonusHit = bonusPMF.tailProbGE(need); - phit += pr * pBonusHit; - pmiss += pr * (1 - pBonusHit); - } - const psuccess = phit + pcrit; - return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss }; - } - resolve(eps = EPS) { - const { - pHit, - pCrit, - pMiss: pmiss - } = this.resolveProbabilities(this.check, eps); - const hitPMF = this.hitEffect ? this.hitEffect instanceof ParsedRollBuilder ? this.hitEffect.toPMF(eps) : pmfFromRollBuilder(this.hitEffect, eps) : PMF.delta(0, eps); - let critPMF = null; - let phit = pHit; - let pcrit = pCrit; - if (this.critEffect === null) { - critPMF = null; - phit += pcrit; - pcrit = 0; - } else { - let critBuilder; - if (this.critEffect) { - critBuilder = this.critEffect; - } else if (this.hitEffect instanceof ParsedRollBuilder) { - critPMF = null; - phit += pcrit; - pcrit = 0; - critBuilder = void 0; - } else { - critBuilder = this.hitEffect?.copy().doubleDice(); - } - if (critBuilder) { - critPMF = critBuilder instanceof ParsedRollBuilder ? critBuilder.toPMF(eps) : pmfFromRollBuilder(critBuilder, eps); - } - } - const missPMF = this.missEffect ? this.missEffect instanceof ParsedRollBuilder ? this.missEffect.toPMF(eps) : pmfFromRollBuilder(this.missEffect, eps) : PMF.delta(0, eps); - const mix = new Mixture(eps); - if (phit > 0) mix.add("hit", hitPMF, phit); - if (critPMF && pcrit > 0) mix.add("crit", critPMF, pcrit); - if (pmiss > 0) - mix.add(this.missEffect ? "missDamage" : "missNone", missPMF, pmiss); - return { - pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps), - check: this.check.toPMF(eps) ?? PMF.delta(0, eps), - hit: hitPMF ?? PMF.delta(0, eps), - crit: critPMF ?? PMF.delta(0, eps), - miss: missPMF ?? PMF.delta(0, eps), - weights: { hit: phit, crit: pcrit, miss: pmiss } - }; - } - // By default, create PMF with no pruning - toPMF(eps = 0) { - return this.resolve(eps).pmf; - } - get pmf() { - return this.toPMF(); - } - // By default, create query on PMF with no pruning - toQuery(eps = 0) { - return this.toPMF(eps).query(); - } -}; - -// src/builder/ac.ts -var ACBuilder = class _ACBuilder extends RollBuilder { - constructor(baseRoll, ac, attackConfig) { - super(baseRoll.getSubRollConfigs()); - if (attackConfig) { - this.attackConfig = { ...attackConfig, ac }; - } else { - this.attackConfig = { ac, critThreshold: 20 }; - } - } - onHit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new AttackBuilder(this, damageRoll); - } - get critThreshold() { - return this.attackConfig.critThreshold; - } - // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? - critOn(threshold) { - const newConfig = { - ...this.attackConfig, - critThreshold: threshold - }; - return new _ACBuilder(this, this.attackConfig.ac, newConfig); - } - alwaysCrits() { - return new AlwaysCritBuilder( - this, - { - critThreshold: this.attackConfig.critThreshold, - ac: this.attackConfig.ac - }, - false - ); - } - // Legacy expressions - toExpression() { - const configs = this.getSubRollConfigs(); - const expression = new RollBuilder(configs).toExpression(); - return this.attackConfig.ac ? `(${expression} AC ${this.attackConfig.ac})` : expression; - } - toPMF(eps = 0) { - const ac = this.attackConfig.ac; - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - const d202 = d20RollPMF(rollType, rerollOne); - const staticMod = this.modifier; - const bonusPMFs = this.getBonusDicePMFs(this, eps); - const parts = [d202, ...bonusPMFs]; - let attackRollPMF = parts.length === 1 ? d202 : PMF.convolveMany(parts, eps); - if (staticMod !== 0) - attackRollPMF = attackRollPMF.mapDamage( - (rollValue) => rollValue + staticMod - ); - const out = /* @__PURE__ */ new Map(); - for (const rollValue of attackRollPMF.support()) { - const p = attackRollPMF.pAt(rollValue); - const key = rollValue >= ac ? rollValue : 0; - out.set(key, (out.get(key) || 0) + p); - } - return PMF.fromMap(out, eps); - } - copy() { - const baseCopy = new RollBuilder(this.getSubRollConfigs()); - const newConfig = { - ac: this.attackConfig.ac, - critThreshold: this.attackConfig.critThreshold - }; - return new _ACBuilder(baseCopy, newConfig.ac, newConfig); - } -}; -RollBuilder.prototype.ac = function(targetAC) { - if (isNaN(targetAC)) throw new Error("Invalid NaN value for targetAC"); - return new ACBuilder(this, targetAC); -}; - -// src/builder/save.ts -var SaveBuilder = class _SaveBuilder { - constructor(check, failureEffect, saveOutcome = "normal") { - this.check = check; - this.failureEffect = failureEffect; - this.saveOutcome = saveOutcome; - } - saveHalf() { - return new _SaveBuilder(this.check, this.failureEffect, "half"); - } - toExpression() { - const checkPart = this.check.toExpression(); - if (!this.failureEffect) return checkPart; - const failureEffectPart = this.failureEffect.toExpression(); - const result = `${checkPart} * (${failureEffectPart})`; - return this.saveOutcome === "half" ? `${result} save half` : result; - } - resolve(eps = EPS) { - const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities( - this.check - ); - const failPMF = this.failureEffect ? this.failureEffect instanceof ParsedRollBuilder ? this.failureEffect.toPMF(eps) : pmfFromRollBuilder(this.failureEffect) : PMF.delta(0); - const onSuccess = this.saveOutcome ?? "half"; - let successPMF = PMF.delta(0, eps); - if (onSuccess === "half") successPMF = failPMF.scaleDamage(0.5, "floor"); - const successLabel = onSuccess === "normal" ? "missNone" : "saveHalf"; - const failLabel = "saveFail"; - const baseMix = new Mixture(eps); - const mixture = baseMix.add(successLabel, successPMF, psuccess).add(failLabel, failPMF, pfail); - return { - pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps), - check: PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps), - saveFail: failPMF ?? PMF.delta(0, eps), - saveSuccess: successPMF ?? PMF.delta(0, eps), - weights: { success: psuccess, fail: pfail } - }; - } - // By default, create PMF with no pruning - toPMF(eps = 0) { - return this.resolve(eps).pmf; - } - get pmf() { - return this.toPMF(); - } - // By default, create query on PMF with no pruning - toQuery(eps = 0) { - return this.toPMF(eps).query(); - } -}; -function resolveProbabilities(check) { - const saveBonus = check.modifier; - const dc = check.saveDC; - const d20Type = check.rollType; - const baseReroll = check.baseReroll; - const die = d20RollPMF(d20Type, baseReroll > 0); - const faceP = /* @__PURE__ */ new Map(); - for (const [r, bin] of die) { - const pr = bin.p; - if (pr > 0) faceP.set(r, pr); - } - const eps = 0; - const bonusDicePMFs = check.getBonusDicePMFs(check, eps); - const bonusPMF = bonusDicePMFs.length > 0 ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.zero(eps); - let pSuccess = 0; - for (let r = 1; r <= 20; r++) { - const pr = faceP.get(r); - if (!pr) continue; - const need = dc - saveBonus - r; - pSuccess += pr * bonusPMF.tailProbGE(need); - } - const pFail = Math.max(0, 1 - pSuccess); - return { pSuccess, pFail }; -} - -// src/builder/dc.ts -var DCBuilder = class _DCBuilder extends RollBuilder { - constructor(baseRoll, saveConfig) { - super(baseRoll.getSubRollConfigs()); - this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 }; - } - dc(saveDC) { - if (this.rollType && this.rollType === "elven accuracy") { - throw new Error( - "Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead." - ); - } - return new _DCBuilder(this, { dc: saveDC }); - } - get saveDC() { - return this.saveConfig.dc; - } - add(anotherRoll) { - const newBuilder = super.add(anotherRoll); - return new _DCBuilder(newBuilder, this.saveConfig); - } - addRoll(count) { - const newBuilder = super.addRoll(count); - return new _DCBuilder(newBuilder, this.saveConfig); - } - onSaveFailure(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new SaveBuilder(this, damageRoll); - } - withElvenAccuracy() { - throw new Error( - "Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks)." - ); - } - // Legacy expressions - toExpression() { - const subConfigs = this.getSubRollConfigs(); - const allConfigs = [...subConfigs]; - const expression = new RollBuilder(allConfigs).toExpression(); - return `(${expression} DC ${this.saveConfig.dc})`; - } - toPMF(eps = 0) { - const saveDC = this.saveDC; - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - const d202 = d20RollPMF(rollType, rerollOne); - const staticMod = this.modifier; - const bonusDicePMFs = this.getBonusDiceConfigs().map( - (cfg) => pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps) - ); - const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); - let psuccess = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - const need = saveDC - staticMod - r; - psuccess += pr * bonusPMF.tailProbGE(need); - } - const pfail = Math.max(0, 1 - psuccess); - const m = /* @__PURE__ */ new Map([ - [0, psuccess > 0 ? psuccess : 0], - [1, pfail > 0 ? pfail : 0] - ]); - return PMF.fromMap(m, eps); - } -}; -RollBuilder.prototype.dc = function(saveDC) { - if (isNaN(saveDC)) throw new Error("Invalid NaN value for saveDC"); - return new DCBuilder(this).dc(saveDC); -}; - -exports.ACBuilder = ACBuilder; -exports.AlwaysCritBuilder = AlwaysCritBuilder; -exports.AlwaysHitBuilder = AlwaysHitBuilder; -exports.AttackBuilder = AttackBuilder; -exports.DCBuilder = DCBuilder; -exports.HalfRollBuilder = HalfRollBuilder; -exports.MaxOfRollBuilder = MaxOfRollBuilder; -exports.ParsedRollBuilder = ParsedRollBuilder; -exports.PooledRollBuilder = PooledRollBuilder; -exports.RollBuilder = RollBuilder; -exports.SaveBuilder = SaveBuilder; -exports.ScaleRollBuilder = ScaleRollBuilder; -exports.builderPMFCache = builderPMFCache; -exports.d = d; -exports.d10 = d10; -exports.d100 = d100; -exports.d12 = d12; -exports.d20 = d20; -exports.d4 = d4; -exports.d6 = d6; -exports.d8 = d8; -exports.defaultConfig = defaultConfig; -exports.flat = flat; -exports.hd20 = hd20; -exports.roll = roll; -exports.sumRolls = sumRolls; -//# sourceMappingURL=index.cjs.map -//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/dist/builder/index.cjs.map b/dist/builder/index.cjs.map deleted file mode 100644 index 5bd91ca..0000000 --- a/dist/builder/index.cjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/common/lru-cache.ts","../../src/common/types.ts","../../src/pmf/query.ts","../../src/pmf/pmf.ts","../../src/pmf/mixture.ts","../../src/common/errors.ts","../../src/parser/dice.ts","../../src/parser/parser.ts","../../src/builder/prob.ts","../../src/builder/d20.ts","../../src/builder/roll.ts","../../src/builder/factory.ts","../../src/builder/ast.ts","../../src/builder/attack.ts","../../src/builder/ac.ts","../../src/builder/save.ts","../../src/builder/dc.ts"],"names":["d","cumulativeSum","result","dfs","arr","d20","ac","staticMod","bonusDicePMFs","bonusPMF","pcrit","pmiss"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import { EPS, PMF } from \"../\";\n\nexport function d20PmfFromCdf(\n cdfPow: (k: number) => number,\n eps: number = EPS\n): PMF {\n const out = new Map();\n let prev = 0;\n for (let k = 1; k <= 20; k++) {\n const cur = cdfPow(k);\n const pk = cur - prev;\n if (pk > 0) {\n out.set(k, pk);\n }\n prev = cur;\n }\n\n return PMF.fromMap(out, eps);\n}\n","import { EPS, PMF, pmfCache } from \"../\";\nimport { d20PmfFromCdf } from \"./prob\";\nimport type { RollType } from \"./types\";\n\nconst cacheKeyMap: Record = {\n \"flat-flat\": \"d20\",\n \"flat-reroll\": \"hd20\",\n \"advantage-flat\": \"d20 > d20\",\n \"advantage-reroll\": \"hd20 > hd20\",\n \"disadvantage-flat\": \"d20 < d20\",\n \"disadvantage-reroll\": \"hd20 < hd20\",\n \"elven accuracy-flat\": \"d20 > d20 > d20\",\n \"elven accuracy-reroll\": \"hd20 > hd20 > hd20\",\n};\n\n/** Lift a single d20 PMF into advantage, disadvantage, or elven (triple-advantage). */\nexport function d20RollPMF(\n rollType: RollType | undefined,\n rerollOne: boolean = false\n): PMF {\n rollType = rollType || \"flat\";\n const cacheKeyLookup = `${rollType}-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cacheKey = cacheKeyMap[cacheKeyLookup];\n if (!cacheKey) {\n throw new Error(`Invalid roll type: ${rollType}`);\n }\n\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const base = d20PMF(rerollOne);\n if (!rollType || rollType === \"flat\") {\n pmfCache.set(cacheKey, base);\n return base;\n }\n\n const p: number[] = new Array(21).fill(0); // indices 1..20\n for (const [r, rec] of base) {\n const pr = typeof rec === \"number\" ? rec : rec.p;\n if (r >= 1 && r <= 20) p[r] = pr;\n }\n const F: number[] = new Array(21).fill(0);\n for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k];\n\n const eps = 0;\n let result = base;\n if (rollType === \"advantage\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps);\n } else if (rollType === \"elven accuracy\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps);\n } else if (rollType === \"disadvantage\") {\n result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps);\n }\n\n pmfCache.set(cacheKey, result);\n return result;\n}\n\nexport function d20PMF(rerollOne: boolean): PMF {\n const cacheKey = `flat-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const m = new Map();\n const base = 1 / 20;\n const rerollShare = base * base;\n if (!rerollOne) {\n for (let r = 1; r <= 20; r++) {\n m.set(r, base);\n }\n } else {\n for (let r = 1; r <= 20; r++) {\n m.set(r, (r === 1 ? 0 : base) + rerollShare);\n }\n }\n const result = PMF.fromMap(m, EPS);\n pmfCache.set(cacheKey, result);\n return result;\n}\n","import type { ACBuilder } from \"./ac\";\nimport type { CritConfig } from \"../common/types\";\nimport type { DCBuilder } from \"./dc\";\nimport { parse } from \"../parser/parser\";\nimport type { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { astFromRollConfigs, pmfFromRollBuilder } from \"./ast\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport type { ExpressionNode, KeepNode, SumNode } from \"./nodes\";\nimport type { RollConfig, RollType } from \"./types\";\n\nexport const defaultConfig: RollConfig = {\n count: 1,\n sides: 0,\n modifier: 0,\n reroll: 0,\n explode: 0,\n minimum: 0,\n bestOf: 0,\n keep: undefined,\n rollType: \"flat\",\n};\n\nconst rollConfigsEqual = (a: RollConfig, b: RollConfig) => {\n return (\n a.count === b.count &&\n a.sides === b.sides &&\n a.modifier === b.modifier &&\n a.reroll === b.reroll &&\n a.explode === b.explode &&\n a.minimum === b.minimum &&\n a.bestOf === b.bestOf &&\n a.keep === b.keep &&\n a.rollType === b.rollType\n );\n};\n\nconst configComplexityScore = (config: RollConfig) => {\n return (\n (config.reroll > 0 ? 1 : 0) +\n (config.explode > 0 ? 1 : 0) +\n (config.minimum > 0 ? 1 : 0) +\n (config.bestOf > 0 ? 1 : 0) +\n (config.keep !== undefined ? 1 : 0) +\n (config.rollType !== \"flat\" ? 1 : 0)\n );\n};\n\n// Fluent builder for dice to create PMFs with an AST\nexport class RollBuilder {\n protected readonly subRollConfigs: readonly RollConfig[];\n\n constructor(countOrConfigs: number | readonly RollConfig[] = 1) {\n if (typeof countOrConfigs === \"number\") {\n const count = countOrConfigs;\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n this.subRollConfigs = [\n { ...defaultConfig, count, isSubtraction: count < 0 },\n ];\n } else {\n this.subRollConfigs = countOrConfigs.map((c) => ({ ...c }));\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n protected get lastConfig() {\n return this.subRollConfigs[this.subRollConfigs.length - 1];\n }\n\n hasHiddenState(): boolean {\n return false;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.subRollConfigs.map((c: RollConfig) => ({ ...c }));\n }\n\n // for testing\n static fromConfig(config: Partial): RollBuilder {\n return new RollBuilder([{ ...defaultConfig, ...config }]);\n }\n\n static fromConfigs(configs: Partial[]): RollBuilder {\n return new RollBuilder(\n configs.map((config) => ({ ...defaultConfig, ...config }))\n );\n }\n\n static fromArgs(...args: any[]): RollBuilder {\n if (args.length === 1) {\n const arg = args[0];\n if (typeof arg === \"number\") {\n if (isNaN(arg)) throw new Error(\"Invalid NaN value for argument\");\n return new RollBuilder(0).plus(arg);\n }\n if (typeof arg === \"string\") {\n return new ParsedRollBuilder(arg);\n }\n if (arg instanceof RollBuilder) {\n return arg;\n }\n }\n\n if (args.length === 2 || args.length === 3) {\n const [count, sidesOrDie, modifier] = args;\n\n if (typeof count !== \"number\") {\n throw new Error(\"First argument must be a number for multi-arg call\");\n }\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count argument\");\n\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) {\n const result = new RollBuilder(0);\n return modifier !== undefined ? result.plus(modifier) : result;\n }\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return modifier !== undefined\n ? resultBuilder.plus(modifier)\n : resultBuilder;\n } else if (typeof sidesOrDie === \"number\" || sidesOrDie === undefined) {\n if (typeof sidesOrDie === \"number\" && isNaN(sidesOrDie))\n throw new Error(\"Invalid NaN value for sides argument\");\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return modifier !== undefined ? builder.plus(modifier) : builder;\n }\n }\n\n throw new Error(`Invalid arguments passed: ${args.join(\", \")}`);\n }\n\n // --- Core Dice Methods ---\n d(sides: number | undefined): RollBuilder {\n if (sides !== undefined && isNaN(sides))\n throw new Error(\"Invalid NaN value for sides\");\n if (sides === undefined) return this;\n if (this.lastConfig.sides && this.lastConfig.sides > 0) {\n throw new Error(\"Cannot add a die after adding a die\");\n }\n if (sides === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].sides = sides;\n return this.create(newConfigs);\n }\n\n plus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n plus(count: number, die: RollBuilder): RollBuilder;\n plus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n if (typeof modOrRoll === \"number\" && isNaN(modOrRoll))\n throw new Error(\"Invalid NaN value for modOrRoll\");\n if (die instanceof RollBuilder && typeof modOrRoll === \"number\") {\n if (die.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const count = modOrRoll;\n const subRollConfigs = die.getSubRollConfigs();\n if (subRollConfigs.length === 0) return this;\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let rollToAdd = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = rollToAdd\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n rollToAdd = new RollBuilder(negatedConfigs);\n }\n return this.add(rollToAdd);\n }\n\n if (die !== undefined) {\n throw new Error(\"Invalid arguments to plus()\");\n }\n\n if (modOrRoll === undefined) return this;\n if (typeof modOrRoll === \"number\") {\n if (modOrRoll === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].modifier += modOrRoll;\n return this.create(newConfigs);\n }\n return this.add(modOrRoll as RollBuilder);\n }\n\n minus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n minus(count: number, die: RollBuilder): RollBuilder;\n minus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n const isNumber = typeof modOrRoll === \"number\";\n const dieIsRoll = die instanceof RollBuilder;\n if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die);\n\n if (die !== undefined) throw new Error(\"Invalid arguments to minus()\");\n if (modOrRoll === undefined) return this;\n\n return isNumber\n ? this.plus(-modOrRoll)\n : this.plus(-1, modOrRoll as RollBuilder);\n }\n\n /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */\n reroll(value: number): RollBuilder {\n if (isNaN(value)) throw new Error(\"Invalid NaN value for reroll\");\n if (value === this.lastConfig.reroll) return this;\n\n const newConfigs = this.getSubRollConfigs();\n\n newConfigs[newConfigs.length - 1].reroll = value;\n return this.create(newConfigs);\n }\n\n /** Set finite explode count for max-face explosions (Infinity allowed). */\n explode(count: number | undefined = Infinity): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for explode count\");\n if (count === undefined) return this;\n if (count === 0) return this;\n if (count < 0) throw new Error(\"Explode count must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].explode = count;\n return this.create(newConfigs);\n }\n\n /** Apply per-die minimum value (min > 0). */\n minimum(val: number | undefined): RollBuilder {\n if (val !== undefined && isNaN(val))\n throw new Error(\"Invalid NaN value for minimum\");\n if (val === undefined) return this;\n if (val === 0) return this;\n if (val < 0) throw new Error(\"Minimum value must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].minimum = val + 1;\n return this.create(newConfigs);\n }\n\n bestOf(count: number | undefined): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for bestOf count\");\n if (count === undefined) return this;\n if (count <= 0) throw new Error(\"Best of count must be > 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].bestOf = count;\n return this.create(newConfigs);\n }\n\n keepHighest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"highest\" };\n return this.create(newConfigs);\n }\n\n keepLowest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"lowest\" };\n return this.create(newConfigs);\n }\n\n keepHighestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighestAll\");\n const currentAST = this.toAST();\n // Wrap in SumNode to represent trials, then KeepNode\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"highest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kh${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n keepLowestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowestAll\");\n const currentAST = this.toAST();\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"lowest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kl${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n withAdvantage(): RollBuilder {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"advantage\";\n return this.create(newConfigs);\n }\n\n withDisadvantage(): RollBuilder {\n const configs = this.getSubRollConfigs();\n configs[configs.length - 1].rollType = \"disadvantage\";\n return this.create(configs);\n }\n\n add(anotherRoll: RollBuilder | undefined): RollBuilder {\n if (anotherRoll === undefined) return this;\n if (anotherRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll).\"\n );\n }\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n withBonus(anotherRoll: RollBuilder): RollBuilder {\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n addRoll(count: number = 1): RollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n const configs = [\n ...this.subRollConfigs,\n {\n ...defaultConfig,\n count,\n isSubtraction: count < 0,\n },\n ];\n return this.create(configs);\n }\n\n scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n const newConfigs = this.getSubRollConfigs().map((config) => {\n if (!config.sides || config.sides <= 0) return config;\n return { ...config, count: config.count * scaleInt };\n });\n return this.create(newConfigs);\n }\n\n doubleDice(): RollBuilder {\n return this.scaleDice(2);\n }\n\n alwaysHits() {\n return new AlwaysHitBuilder(this);\n }\n\n alwaysCrits() {\n return new AlwaysCritBuilder(this);\n }\n\n copy(): RollBuilder {\n return this.create(this.getSubRollConfigs());\n }\n\n // --- Dice Shortcut Methods ---\n d4 = () => this.d(4);\n d6 = () => this.d(6);\n d8 = () => this.d(8);\n d10 = () => this.d(10);\n d12 = () => this.d(12);\n d20 = () => this.d(20);\n d100 = () => this.d(100);\n\n withElvenAccuracy() {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"elven accuracy\";\n return this.create(newConfigs);\n }\n\n toExpression(): string {\n const originalDiceConfigs = this.subRollConfigs.filter(\n (config) => config.sides && config.sides > 0\n );\n\n type Group = { config: RollConfig; totalCount: number };\n const configGroups = new Map();\n\n for (const config of originalDiceConfigs) {\n const keyConfig: Partial = { ...config };\n delete keyConfig.count;\n delete keyConfig.modifier;\n const key = JSON.stringify(keyConfig);\n\n const existingGroup = configGroups.get(key);\n if (existingGroup) {\n existingGroup.totalCount += config.count;\n } else {\n configGroups.set(key, { config, totalCount: config.count });\n }\n }\n\n const rootConfig = this.getRootDieConfig();\n const groupedConfigs = Array.from(configGroups.values());\n let rootD20Group: Group | undefined;\n\n if (rootConfig && rootConfig.sides === 20) {\n const rootIndex = groupedConfigs.findIndex(\n ({ config }) =>\n rollConfigsEqual(config, rootConfig) &&\n JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep)\n );\n\n if (rootIndex !== -1) {\n rootD20Group = groupedConfigs.splice(rootIndex, 1)[0];\n }\n }\n\n const sortedDiceConfigs = groupedConfigs\n .map(({ config, totalCount }) => ({\n ...config,\n count: totalCount,\n }))\n .sort((a, b) => {\n const aHasPriority = a.reroll > 0 || a.minimum > 0;\n const bHasPriority = b.reroll > 0 || b.minimum > 0;\n if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1;\n if (b.sides !== a.sides) return b.sides - a.sides;\n return configComplexityScore(b) - configComplexityScore(a);\n });\n\n const diceConfigs = rootD20Group\n ? [\n { ...rootD20Group.config, count: rootD20Group.totalCount },\n ...sortedDiceConfigs,\n ]\n : sortedDiceConfigs;\n\n const totalModifier = this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n if (diceConfigs.length === 0) return totalModifier.toString();\n\n const rootDieConfig = this.getRootDieConfig();\n const newRootConfig = rootDieConfig\n ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig))\n : undefined;\n\n // Generate dice expressions without individual modifiers\n const diceExpressions = diceConfigs.map((config) =>\n this.configToSingleExpressionWithoutModifier(\n config,\n config === newRootConfig\n )\n );\n\n // Join dice expressions with appropriate operators based on their count\n let result = \"\";\n for (let i = 0; i < diceExpressions.length; i++) {\n const config = diceConfigs[i];\n const expression = diceExpressions[i];\n\n if (i === 0) {\n result = (config.isSubtraction ? \"-\" : \"\") + expression;\n\n // Add constants right after the root d20 die (if it's a d20)\n if (config.sides === 20 && totalModifier !== 0) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else result += ` - ${Math.abs(totalModifier)}`;\n }\n } else {\n // Use minus sign for negative subtraction, plus sign otherwise\n const operator = config.isSubtraction ? \" - \" : \" + \";\n result += operator + expression;\n }\n }\n\n // If constants weren't added after d20, add them at the end\n if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`;\n }\n\n return result.replace(/\\+ -/g, \"-\");\n }\n\n toPMF(eps: number = 0): PMF {\n // Main AST entry point\n return pmfFromRollBuilder(this, eps);\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n\n toAST(): ExpressionNode {\n const configs = this.getSubRollConfigs();\n return (\n astFromRollConfigs(configs) ||\n ({ type: \"constant\", value: 0 } as ExpressionNode)\n );\n }\n\n private configToSingleExpressionWithoutModifier(\n config: RollConfig,\n isRootDie: boolean\n ): string {\n if (!config.sides || config.sides <= 0) return \"\";\n\n let baseDie = `d${config.sides}`;\n\n if (config.reroll > 0) {\n if (config.minimum > 0 && config.explode > 0) {\n // Complex single roll case: minimum + explode + reroll\n // Apply reroll after minimum is applied\n } else if (config.minimum > 0) {\n // When there's a minimum but no explode, use descending order and apply before minimum\n for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`;\n } else {\n // When there's no minimum, use ascending order\n for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`;\n }\n }\n\n if (config.minimum > 0) {\n if (config.reroll > 0 && !config.explode) {\n baseDie = `${config.minimum}>(${baseDie})`;\n } else {\n baseDie = `${config.minimum}>${baseDie}`;\n }\n if (config.reroll > 0 && config.explode > 0) {\n for (let i = 1; i <= config.reroll; i++) {\n baseDie += ` reroll ${i}`;\n }\n }\n }\n\n // Check for hd20 shorthand AFTER adding explode\n if (baseDie === \"d20 reroll 1\" && config.minimum <= 1) baseDie = \"hd20\";\n\n let mainExpression = \"\";\n switch (config.rollType) {\n case \"advantage\":\n mainExpression = `${baseDie} > ${baseDie}`;\n break;\n case \"disadvantage\":\n mainExpression = `${baseDie} < ${baseDie}`;\n break;\n case \"elven accuracy\":\n mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`;\n break;\n case \"flat\":\n if (config.keep) {\n const mode = config.keep.mode === \"highest\" ? \"kh\" : \"kl\";\n const baseDieExpression =\n this.configToSingleExpressionWithoutModifier(\n {\n ...config,\n count: config.count,\n modifier: 0,\n rollType: \"flat\",\n keep: undefined,\n },\n false\n );\n mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`;\n } else {\n const isComplex = baseDie.length > `d${config.sides}`.length;\n const isHalflingShorthand = baseDie === \"hd20\";\n const isD20Shorthand = baseDie === \"d20\" && isRootDie;\n const hasMinimum = config.minimum > 0;\n const hasReroll = config.reroll > 0;\n // For negative subtraction, use absolute value for display\n // For negative counts from factory function, treat as 1 (legacy behavior)\n const effectiveCount = config.isSubtraction\n ? Math.abs(config.count)\n : config.count < 0\n ? 1\n : Math.abs(config.count);\n\n if (effectiveCount > 1) {\n const shouldAddParentheses = isComplex;\n mainExpression = shouldAddParentheses\n ? `${effectiveCount}(${baseDie})`\n : `${effectiveCount}${baseDie}`;\n } else if (effectiveCount === 1) {\n const needsParens = hasReroll && hasMinimum;\n if (config.isSubtraction) {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n } else if (\n isComplex ||\n isHalflingShorthand ||\n isD20Shorthand ||\n config.count < 0\n ) {\n mainExpression = needsParens ? `1(${baseDie})` : baseDie;\n } else {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n }\n } else {\n mainExpression = baseDie;\n }\n }\n if (config.bestOf && config.count && config.bestOf < config.count) {\n mainExpression += `kh${config.bestOf}`;\n }\n break;\n }\n\n return mainExpression;\n }\n\n getRootDieConfig(): RollConfig | undefined {\n const configs = this.subRollConfigs;\n return configs.find((config) => config.sides > 0) || configs[0];\n }\n\n getAllDieConfigs(): readonly RollConfig[] {\n return this.getSubRollConfigs();\n }\n\n getBonusDiceConfigs(): RollConfig[] {\n const allConfigs = this.subRollConfigs;\n const rootConfig =\n allConfigs.find((config) => config.sides > 0) || allConfigs[0];\n if (!rootConfig) return [];\n return allConfigs\n .filter((config) => config.sides > 0)\n .filter((config) => config !== rootConfig);\n }\n\n getBonusDicePMFs(check: RollBuilder, eps: number = 0): PMF[] {\n return check\n .getBonusDiceConfigs()\n .map((config) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([config]), eps)\n );\n }\n\n get modifier(): number {\n return this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n }\n\n get rollType(): RollType {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.rollType || \"flat\";\n }\n\n get baseReroll(): number {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.reroll || 0;\n }\n\n half(): HalfRollBuilder {\n return new HalfRollBuilder(this);\n }\n\n /**\n * Scale this roll's result by `numerator / denominator`, rounding each outcome.\n * A general, composable form of {@link half} — used to model damage-type resistance\n * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`).\n * Compose several of these (and plain rolls) into one payload with {@link sumRolls}.\n */\n scaleResult(\n numerator: number,\n denominator: number = 1,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): ScaleRollBuilder {\n return new ScaleRollBuilder(this, numerator, denominator, rounding);\n }\n\n // Create a \"max of N rolls\" version of this roll for crit damage with keep operations\n maxOf(count: number): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this, count);\n }\n\n // These methods are implemented via prototype augmentation in ac.ts and dc.ts\n // They are declared here to provide proper TypeScript types\n ac(_targetAC: number): ACBuilder {\n throw new Error(\"ac() should be implemented via prototype augmentation\");\n }\n\n dc(_saveDC: number): DCBuilder {\n throw new Error(\"dc() should be implemented via prototype augmentation\");\n }\n}\n\nexport class HalfRollBuilder extends RollBuilder {\n constructor(private readonly innerRoll: RollBuilder) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n // No need to override create if we don't expose RollBuilder methods that use it,\n // but HalfRollBuilder extends RollBuilder so it does.\n // However, HalfRollBuilder seems to just wrap another roll.\n // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder?\n // No, RollBuilder.plus returns RollBuilder.\n // The inheritance here is a bit tricky.\n // Existing code for HalfRollBuilder doesn't seem to implement plus/etc.\n // So .plus() on a HalfRollBuilder would return a RollBuilder (base class).\n // Which is fine.\n // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that.\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const innerExpression = this.innerRoll.toExpression();\n return `(${innerExpression}) // 2`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"half\",\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): HalfRollBuilder {\n return new HalfRollBuilder(this.innerRoll.copy());\n }\n}\n\n/**\n * A roll whose result is scaled by `numerator / denominator` and rounded — the composable\n * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or\n * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls.\n */\nexport class ScaleRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly numerator: number,\n private readonly denominator: number = 1,\n private readonly rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const inner = this.innerRoll.toExpression();\n if (this.denominator === 1) return `${this.numerator} * (${inner})`;\n if (this.numerator === 1) return `(${inner}) // ${this.denominator}`;\n return `(${inner}) * ${this.numerator} // ${this.denominator}`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"scale\",\n numerator: this.numerator,\n denominator: this.denominator,\n rounding: this.rounding,\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): ScaleRollBuilder {\n return new ScaleRollBuilder(\n this.innerRoll.copy(),\n this.numerator,\n this.denominator,\n this.rounding\n );\n }\n}\n\nexport class MaxOfRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly count: number,\n private readonly diceCount?: number,\n private readonly diceSides?: number\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n // Use the stored dice info to create the expression directly\n if (this.diceCount && this.diceSides) {\n return `max${this.count}(${this.diceCount}d${this.diceSides})`;\n }\n\n // If no stored dice info, fallback to simple max expression\n return `max${this.count}(?d?)`;\n }\n\n toAST(): ExpressionNode {\n // Use the stored dice info if available\n if (this.diceCount && this.diceSides) {\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: this.diceCount,\n child: { type: \"die\", sides: this.diceSides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n\n // Fallback: try to get from innerRoll\n try {\n const configs = this.innerRoll.getSubRollConfigs();\n if (configs.length === 1 && configs[0].sides) {\n const config = configs[0];\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: config.count,\n child: { type: \"die\", sides: config.sides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n } catch {\n // Last resort: try parsing the expression (though this shouldn't work with current RollBuilder)\n }\n\n // Fallback - this shouldn't happen in normal usage\n throw new Error(\n `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`\n );\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this.innerRoll.copy(), this.count);\n }\n}\n\nexport class AlwaysHitBuilder extends RollBuilder {\n readonly attackConfig: CritConfig;\n\n constructor(baseRoll: RollBuilder, attackConfig?: CritConfig) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysHitBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(critThreshold: number): AlwaysHitBuilder {\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(this, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(this, undefined, true);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysHitBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(baseCopy, newConfig);\n }\n}\n\nexport class AlwaysCritBuilder extends RollBuilder {\n readonly attackConfig: CritConfig & { ac?: number };\n readonly fromAlwaysHit: boolean;\n\n constructor(\n baseRoll: RollBuilder,\n attackConfig?: CritConfig & { ac?: number },\n fromAlwaysHit: boolean = false\n ) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysCritBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n critOn(critThreshold: number): AlwaysCritBuilder {\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysCritBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit);\n }\n}\n\nexport class ParsedRollBuilder extends RollBuilder {\n private readonly cachedPMF: PMF;\n private readonly originalExpression: string;\n\n constructor(expression: string) {\n super([]); // Empty configs since we're bypassing the normal builder flow\n this.originalExpression = expression;\n this.cachedPMF = parse(expression, 0);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n override toPMF(_eps: number = 0): PMF {\n // Return the pre-computed PMF, ignoring epsilon for now\n // The parse() function was already called with eps=0\n return this.cachedPMF;\n }\n\n override toExpression(): string {\n return this.originalExpression;\n }\n\n override toAST(): ExpressionNode {\n // Since we don't have the actual AST structure, return a constant node\n // This is a limitation but shouldn't matter for terminal damage expressions\n throw new Error(\n \"ParsedRollBuilder does not support AST conversion. Use the builder API instead.\"\n );\n }\n\n override copy(): ParsedRollBuilder {\n return new ParsedRollBuilder(this.originalExpression);\n }\n\n override doubleDice(): ParsedRollBuilder {\n throw new Error(\n \"ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead.\"\n );\n }\n}\n\nexport class PooledRollBuilder extends RollBuilder {\n constructor(\n private readonly baseAST: ExpressionNode,\n private readonly baseExpression: string,\n configs: readonly RollConfig[] = []\n ) {\n // Initialize with empty config if none provided\n super(configs.length > 0 ? configs : 0);\n }\n\n protected create(configs: readonly RollConfig[]): PooledRollBuilder {\n // This is the key fix: we preserve the baseAST and baseExpression\n // and only update the configs\n return new PooledRollBuilder(this.baseAST, this.baseExpression, configs);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override d(_sides: number | undefined): RollBuilder {\n throw new Error(\"Cannot add dice to a pooled roll. The pool is finalized.\");\n }\n\n override reroll(_value: number): RollBuilder {\n throw new Error(\"Cannot set reroll on a pooled roll.\");\n }\n\n override explode(_count: number | undefined = Infinity): RollBuilder {\n throw new Error(\"Cannot set explode on a pooled roll.\");\n }\n\n override minimum(_val: number | undefined): RollBuilder {\n throw new Error(\"Cannot set minimum on a pooled roll.\");\n }\n\n override bestOf(_count: number | undefined): RollBuilder {\n throw new Error(\"Cannot set bestOf on a pooled roll.\");\n }\n\n override keepHighest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling.\"\n );\n }\n\n override keepLowest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling.\"\n );\n }\n\n override withAdvantage(): RollBuilder {\n throw new Error(\"Cannot set advantage on a pooled roll.\");\n }\n\n override withDisadvantage(): RollBuilder {\n throw new Error(\"Cannot set disadvantage on a pooled roll.\");\n }\n\n override withElvenAccuracy(): RollBuilder {\n throw new Error(\"Cannot set elven accuracy on a pooled roll.\");\n }\n\n override toAST(): ExpressionNode {\n const configsAST = super.toAST();\n\n // Check if configsAST is effectively zero/empty\n const isZero = configsAST.type === \"constant\" && configsAST.value === 0;\n\n if (isZero) {\n return this.baseAST;\n }\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [\n { node: this.baseAST, sign: 1 },\n { node: configsAST, sign: 1 },\n ];\n\n return { type: \"add\", children };\n }\n\n override toExpression(): string {\n const configsExpression = super.toExpression();\n\n // If no configs added, just return base expression\n if (configsExpression === \"0\") {\n return this.baseExpression;\n }\n\n // Clean up the join\n if (configsExpression.startsWith(\"-\")) {\n // If it's a negative number/expression, format as \" - value\"\n // configsExpression is like \"-2\" or \"-1d6\"\n return `${this.baseExpression} - ${configsExpression.substring(1)}`;\n }\n return `${this.baseExpression} + ${configsExpression}`;\n }\n\n override copy(): PooledRollBuilder {\n return new PooledRollBuilder(\n this.baseAST,\n this.baseExpression,\n this.getSubRollConfigs()\n );\n }\n\n override scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n // Scale the base pool (treat it as a die/unit)\n // We wrap the base AST in a SumNode\n const newBaseAST: SumNode = {\n type: \"sum\",\n count: scaleInt,\n child: this.baseAST,\n };\n const newBaseExpr =\n scaleInt === 1\n ? this.baseExpression\n : `${scaleInt}(${this.baseExpression})`;\n\n // We preserve the existing modifiers (subRollConfigs) without scaling them,\n // because scaleDice() generally only scales \"dice\", not flat modifiers.\n // Since we forbid adding dice to PooledRollBuilder, subRollConfigs are only modifiers.\n return new PooledRollBuilder(\n newBaseAST,\n newBaseExpr,\n this.getSubRollConfigs()\n );\n }\n\n times(count: number): PooledRollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for times\");\n if (Math.floor(count) !== count)\n throw new Error(\"times() requires an integer\");\n if (count < 0) throw new Error(\"times() requires a non-negative integer\");\n\n // We wrap the current state (base + modifiers) into a new pool repeated N times\n const currentAST = this.toAST();\n const currentExpr = this.toExpression();\n\n const sumNode: SumNode = {\n type: \"sum\",\n count,\n child: currentAST,\n };\n\n const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`;\n\n return new PooledRollBuilder(sumNode, newExpr);\n }\n}\n\n/**\n * An additive composite of independent rolls that preserves each part's AST — the piece\n * that lets a scaled/halved sub-roll (which the flat `.plus()` merge would otherwise drop)\n * sit beside plain rolls in one damage payload. Its PMF convolves the parts; its expression\n * joins them with ` + `. Built via {@link sumRolls}; terminal (used as an onHit/onCrit/\n * onSaveFailure payload), so it reports hidden state to reject accidental flat merges.\n */\nclass CompositeSumRollBuilder extends RollBuilder {\n constructor(private readonly parts: readonly RollBuilder[]) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override getSubRollConfigs(): readonly RollConfig[] {\n return [];\n }\n\n override toAST(): ExpressionNode {\n return {\n type: \"add\",\n children: this.parts.map((p) => ({\n node: p.toAST(),\n sign: 1 as const,\n })),\n };\n }\n\n override toExpression(): string {\n const exprs = this.parts\n .map((p) => p.toExpression())\n .filter((e) => e && e !== \"0\");\n if (exprs.length === 0) return \"0\";\n let result = exprs[0];\n for (let i = 1; i < exprs.length; i++) {\n const e = exprs[i];\n result += e.startsWith(\"-\") ? ` - ${e.substring(1)}` : ` + ${e}`;\n }\n return result.replace(/\\+ -/g, \"-\");\n }\n\n override toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n override copy(): CompositeSumRollBuilder {\n return new CompositeSumRollBuilder(this.parts.map((p) => p.copy()));\n }\n}\n\n/**\n * Combine several rolls into one additive payload whose PMF is their convolution and whose\n * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry\n * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance\n * and vulnerability survive into both the distribution and the rendered expression.\n * Empty parts collapse to `0`; a single part is returned unwrapped.\n */\nexport function sumRolls(parts: readonly RollBuilder[]): RollBuilder {\n const meaningful = parts.filter((p): p is RollBuilder => p !== undefined);\n if (meaningful.length === 0) return new RollBuilder(0);\n if (meaningful.length === 1) return meaningful[0];\n return new CompositeSumRollBuilder(meaningful);\n}\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { RollBuilder } from \"./roll\";\nimport type { RollFactory } from \"./types\";\n\nconst rollFn = (\n count: number,\n sidesOrDie?: number | RollBuilder,\n modifier?: number\n): RollBuilder => {\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n // roll(2, d6, 5)\n // Create a new config, using the base die's config but overriding the count\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier);\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return resultBuilder.plus(modifier);\n } else {\n // roll(2, 6, 5)\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return builder.plus(modifier);\n }\n};\n\nrollFn.d = (sides: number | string): RollBuilder => {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n};\nrollFn.hd20 = (): RollBuilder => new RollBuilder(1).d20().reroll(1);\nrollFn.d4 = (): RollBuilder => new RollBuilder(1).d4();\nrollFn.d6 = (): RollBuilder => new RollBuilder(1).d6();\nrollFn.d8 = (): RollBuilder => new RollBuilder(1).d8();\nrollFn.d10 = (): RollBuilder => new RollBuilder(1).d10();\nrollFn.d12 = (): RollBuilder => new RollBuilder(1).d12();\nrollFn.d20 = (): RollBuilder => new RollBuilder(1).d20();\nrollFn.d100 = (): RollBuilder => new RollBuilder(1).d100();\nrollFn.flat = (n: number): RollBuilder => new RollBuilder(0).plus(n);\n\nexport function d(sides: number | string): RollBuilder {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n}\n\nexport const d4 = new RollBuilder(1).d4();\nexport const d6 = new RollBuilder(1).d6();\nexport const d8 = new RollBuilder(1).d8();\nexport const d10 = new RollBuilder(1).d10();\nexport const d12 = new RollBuilder(1).d12();\nexport const d20 = new RollBuilder(1).d20();\nexport const hd20 = new RollBuilder(1).d20().reroll(1);\nexport const d100 = new RollBuilder(1).d100();\nexport const flat = (n: number) => new RollBuilder(0).plus(n);\n\nexport const roll: RollFactory = rollFn as RollFactory;\n\nexport const builderPMFCache = new LRUCache(1000);\n","import { LRUCache, PMF } from \"../\";\nimport { d20RollPMF } from \"./d20\";\nimport { builderPMFCache } from \"./factory\";\nimport type {\n AddNode,\n ConstantNode,\n D20RollNode,\n DieNode,\n ExpressionNode,\n KeepNode,\n MaxOfNode,\n SumNode,\n} from \"./nodes\";\nimport type { RollBuilder } from \"./roll\";\nimport type { RollConfig } from \"./types\";\n\n// For now, default to 0 epsilon. Later we can tighten to EPS.\nconst defaultEps = 0;\n\nconst singleDiePMFCache = new LRUCache(1000);\n\nexport function astFromRollConfigs(\n configs: readonly RollConfig[]\n): ExpressionNode | undefined {\n // TODO add cache for this\n if (!configs || configs.length === 0) return undefined;\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [];\n let constantSum = 0;\n\n for (const cfg of configs) {\n const sign: 1 | -1 = cfg.isSubtraction || cfg.count < 0 ? -1 : 1;\n const count = Math.abs(cfg.count || 0);\n\n constantSum += cfg.modifier || 0;\n\n if ((cfg.sides || 0) <= 0) continue;\n\n const die: DieNode = {\n type: \"die\",\n sides: cfg.sides,\n reroll: cfg.reroll > 0 ? cfg.reroll : undefined,\n minimum: cfg.minimum > 0 ? cfg.minimum : undefined,\n explode:\n cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0\n ? cfg.explode\n : undefined,\n };\n\n let node: ExpressionNode = die;\n\n let appliedRollType = false;\n if (cfg.rollType && cfg.rollType !== \"flat\") {\n if (cfg.sides === 20) {\n node = {\n type: \"d20Roll\",\n rollType: cfg.rollType,\n child: node,\n } as D20RollNode;\n } else {\n const n = cfg.rollType === \"elven accuracy\" ? 3 : 2;\n const mode = cfg.rollType === \"disadvantage\" ? \"lowest\" : \"highest\";\n const base: SumNode = { type: \"sum\", count: n, child: node };\n node = { type: \"keep\", mode, count: 1, child: base } as KeepNode;\n }\n appliedRollType = true;\n }\n\n if (cfg.rollType === \"flat\" && cfg.keep && cfg.keep.total > 0) {\n const baseCount = Math.max(1, Math.floor(Math.abs(count || 1)));\n const trials = Math.max(1, Math.floor(cfg.keep.total));\n const k = Math.max(0, Math.floor(cfg.keep.count));\n\n // For keep-highest of 1, always treat as trials-of-sums: max over trial sums\n if (k === 1 && cfg.keep.mode === \"highest\") {\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n node = {\n type: \"maxOf\",\n count: trials,\n child: perTrial,\n } as MaxOfNode;\n }\n } else if (trials === baseCount) {\n // Classic pool: keep K of N faces from N iid dice\n const base: SumNode = { type: \"sum\", count: trials, child: node };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: base,\n } as KeepNode;\n } else {\n // General trials-of-sums: trials of (baseCount dice sum), keep K trial sums\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n const trialPool: SumNode = {\n type: \"sum\",\n count: trials,\n child: perTrial,\n };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: trialPool,\n } as KeepNode;\n }\n }\n } else {\n const c = appliedRollType ? 1 : Math.max(1, count || 1);\n node = { type: \"sum\", count: c, child: node } as SumNode;\n }\n\n children.push({ node, sign });\n }\n\n if (children.length === 0) {\n return { type: \"constant\", value: constantSum } as ConstantNode;\n }\n\n const add: AddNode = { type: \"add\", children };\n if (constantSum !== 0)\n add.children.push({\n node: { type: \"constant\", value: constantSum },\n sign: 1,\n });\n return add;\n}\n\nexport function resolve(node: ExpressionNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(node);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n const result = ((): PMF => {\n switch (node.type) {\n case \"constant\":\n return PMF.delta(node.value, eps);\n\n case \"die\": {\n return resolveSingleDie(node, eps);\n }\n\n case \"sum\": {\n const base = resolve(node.child, eps);\n const n = Math.max(0, Math.floor(node.count));\n if (n === 0) return PMF.delta(0, eps);\n if (n === 1) return base;\n return base.power(n, eps);\n }\n\n case \"add\": {\n let shift = 0;\n const parts: PMF[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n shift += c.sign * c.node.value;\n } else {\n const p = resolve(c.node, eps);\n parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v));\n }\n }\n if (parts.length === 0) return PMF.delta(shift, eps);\n let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps);\n if (shift !== 0) res = res.mapDamage((v) => v + shift);\n return res;\n }\n\n case \"keep\": {\n const totalTrials = getTotalCount(node);\n const keepCount = Math.max(0, Math.min(node.count, totalTrials));\n if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps);\n\n // Resolve the per-trial PMF (the child of the Sum inside Keep)\n const perTrialNode = node.child.child; // Sum(child: perTrial)\n const perTrialPMF = resolve(perTrialNode, eps);\n\n return keepSumPMF(\n perTrialPMF,\n totalTrials,\n keepCount,\n node.mode === \"highest\",\n eps\n );\n }\n\n case \"d20Roll\": {\n const childDie = findDie(node.child);\n const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;\n return d20RollPMF(node.rollType, rerollOne);\n }\n\n case \"half\": {\n const childPMF = resolve(node.child, eps);\n return childPMF.scaleDamage(0.5, \"floor\");\n }\n\n case \"maxOf\": {\n const childPMF = resolve(node.child, eps);\n const count = Math.max(1, Math.floor(node.count));\n if (count === 1) return childPMF;\n\n // Compute the maximum of count independent rolls of childPMF\n return computeMaxOfPMF(childPMF, count, eps);\n }\n\n case \"scale\": {\n const childPMF = resolve(node.child, eps);\n const denom = node.denominator === 0 ? 1 : node.denominator;\n return childPMF.scaleDamage(node.numerator / denom, node.rounding);\n }\n }\n })();\n\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function pmfFromRollBuilder(\n rb: RollBuilder,\n eps: number = defaultEps\n): PMF {\n const ast = rb.toAST();\n return resolve(ast, eps);\n}\n\nfunction resolveSingleDie(die: DieNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(die);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = singleDiePMFCache.get(cacheKey);\n if (cached) return cached;\n\n const s = Math.max(0, Math.floor(die.sides));\n if (s <= 0) return PMF.delta(0, eps);\n\n let probs = new Map();\n for (let v = 1; v <= s; v++) probs.set(v, 1 / s);\n\n // TODO - check if this is correct. Sequential reroll passes? Or at once?\n const r = Math.max(0, Math.floor(die.reroll || 0));\n if (r > 0) {\n const k = Math.min(r, s);\n const rerollMass = k / s; // total probability rerolled once\n const uniformReroll = rerollMass / s; // mass added to each face from reroll\n const next = new Map();\n for (let v = 1; v <= s; v++) {\n const keep = v <= k ? 0 : 1 / s;\n next.set(v, keep + uniformReroll);\n }\n probs = next;\n }\n\n let pmf = PMF.fromMap(new Map(probs), eps);\n\n // Minimum per die\n const minV = Math.max(0, Math.floor(die.minimum || 0));\n if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV));\n\n // Exploding dice (finite) on max face only\n const explode = die.explode;\n if (explode && Number.isFinite(explode) && explode > 0) {\n const times = Math.floor(explode);\n const maxFace = s;\n\n // Split pmf into non-max and max\n const nonMax = new Map();\n const pMax = pmf.pAt(maxFace);\n for (const v of pmf.support()) {\n if (v !== maxFace) nonMax.set(v, pmf.pAt(v));\n }\n let nonMaxPMF = PMF.fromMap(nonMax, eps);\n if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) {\n // keep raw mass composition\n nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax);\n }\n\n // TODO - explosions\n // Additional roll distribution equals original pmf without explosions applied again.\n // For simplicity we treat cascaded explosions as adding uniform max-only triggers.\n // Compute sum of up to `times` additional rolls conditioned on each explosion hit.\n let tail = PMF.delta(0, eps);\n const addOnce = pmf;\n for (let t = 1; t <= times; t++) {\n tail = tail.convolve(addOnce, eps);\n }\n // Mix: with prob (1 - pMax) take nonMax, with prob pMax take maxFace + tail\n const exploded = PMF.branch(\n tail.mapDamage((v) => v + maxFace),\n nonMaxPMF,\n pMax\n );\n pmf = exploded;\n }\n\n singleDiePMFCache.set(cacheKey, pmf);\n return pmf;\n}\n\n// Getters\n\nfunction findDie(node: ExpressionNode): DieNode | undefined {\n switch (node.type) {\n case \"die\":\n return node;\n case \"constant\":\n return undefined;\n case \"sum\":\n case \"d20Roll\":\n case \"half\":\n case \"maxOf\":\n case \"scale\":\n return findDie(node.child);\n case \"keep\":\n return findDie(node.child.child);\n case \"add\":\n for (const c of node.children) {\n const d = findDie(c.node);\n if (d) return d;\n }\n return undefined;\n }\n}\n\nfunction getTotalCount(node: KeepNode): number {\n // The total dice count is encoded in the nearest SumNode under child\n let cur = node.child;\n while (cur.type === \"keep\") cur = cur.child;\n return cur.type === \"sum\" ? Math.max(0, Math.floor(cur.count)) : 0;\n}\n\nfunction computeMaxOfPMF(\n pmf: PMF,\n count: number,\n eps: number = defaultEps\n): PMF {\n // Compute the maximum of 'count' independent rolls of the given PMF\n if (count <= 1) return pmf;\n\n const support = pmf.support();\n const out = new Map();\n\n // For small counts, we can enumerate all outcomes\n if (count <= 6 && support.length <= 20) {\n function dfs(\n rollsLeft: number,\n currentMax: number,\n probability: number\n ): void {\n if (rollsLeft === 0) {\n out.set(currentMax, (out.get(currentMax) || 0) + probability);\n return;\n }\n\n for (const value of support) {\n const p = pmf.pAt(value);\n if (p > 0) {\n const newMax = Math.max(currentMax, value);\n dfs(rollsLeft - 1, newMax, probability * p);\n }\n }\n }\n\n dfs(count, -Infinity, 1);\n } else {\n // For larger cases, use the CDF method. Walk the sorted support once while\n // accumulating a running CDF, so each P(max = v) costs O(1) instead of a\n // full-map cdfAt() scan (previously O(N) per value → O(N²) overall).\n // Between two consecutive support points there is no probability mass, so\n // the running CDF up to (but not including) v equals cdfAt(v - 1).\n const sortedSupport = [...support].sort((a, b) => a - b);\n let runningCdf = 0;\n for (const value of sortedSupport) {\n const prevCdf = runningCdf;\n runningCdf += pmf.pAt(value);\n\n // P(max = value) = P(all rolls <= value) - P(all rolls <= value-1)\n const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count);\n if (probMax > eps) {\n out.set(value, probMax);\n }\n }\n }\n\n return PMF.fromMap(out, eps);\n}\n\nfunction keepSumPMF(\n single: PMF,\n total: number,\n keep: number,\n highest: boolean,\n eps: number = defaultEps\n): PMF {\n // Trivial/fast paths\n if (keep >= total) return single.power(total, eps);\n if (keep <= 0) return PMF.delta(0, eps);\n\n const sortedSupport = [...single.support()].sort((a, b) => a - b);\n const pmfSig = sortedSupport\n .map((val) => `${val}:${single.pAt(val).toPrecision(6)}`)\n .join(\",\");\n const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${\n highest ? 1 : 0\n }|e:${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n // kh1/kl1 fast paths using max-of machinery\n if (keep === 1) {\n if (highest) {\n return computeMaxOfPMF(single, total, eps);\n } else {\n // min of n i.i.d. == -max of n of negated variable\n const neg = single.mapDamage((v) => -v);\n const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v);\n builderPMFCache.set(cacheKey, minPMF);\n return minPMF;\n }\n }\n\n // DP over descending values; state = (used, remainingTrials) → map(sum -> prob)\n // Transition by drawing X occurrences at current value v from remainingTrials r: X ~ Binom(r, p)\n // Select t = min(X, keep - used) into the sum (highest picks first), then continue with r - X.\n\n type SumMap = Map;\n let state: Map = new Map();\n // Pack the (used, remainingTrials) state into a single integer key instead of\n // a \"used|r\" string. r ∈ [0, total], so a stride of (total + 1) is collision\n // free, and decoding is plain integer math — no split()/parseInt() per\n // transition in the hot loop. Behavior is identical (same states, same order).\n const stride = total + 1;\n const keyOf = (used: number, r: number) => used * stride + r;\n\n state.set(keyOf(0, total), new Map([[0, 1]]));\n\n const valuesDesc = highest\n ? [...sortedSupport].sort((a, b) => b - a)\n : [...sortedSupport].sort((a, b) => a - b);\n\n const binomPMF = (r: number, p: number): number[] => {\n if (r <= 0) return [1];\n if (p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[0] = 1;\n return arr;\n }\n if (1 - p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[r] = 1;\n return arr;\n }\n const q = 1 - p;\n const arr = new Array(r + 1).fill(0);\n\n // stable recurrence from k=0\n arr[0] = Math.pow(q, r);\n const ratio = p / q;\n for (let x = 1; x <= r; x++)\n arr[x] = ((arr[x - 1] * (r - x + 1)) / x) * ratio;\n\n // Normalize minor drift\n let s = 0;\n for (let x = 0; x <= r; x++) s += arr[x];\n if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s;\n\n return arr;\n };\n\n const pruneMap = (m: SumMap, threshold: number): SumMap => {\n if (threshold <= 0) return m;\n const out = new Map();\n for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr);\n return out.size === m.size ? m : out;\n };\n\n const pruneState = (st: Map, threshold: number) => {\n if (threshold <= 0) return st;\n const out = new Map();\n for (const [k, m] of st) {\n const mm = pruneMap(m, threshold);\n if (mm.size > 0) out.set(k, mm);\n }\n return out;\n };\n\n let processedMass = 0;\n for (const v of valuesDesc) {\n const p = single.pAt(v);\n if (p <= 0) continue;\n const q = Math.max(eps, 1 - processedMass);\n const pCond = Math.min(1, p / q);\n const next: Map = new Map();\n\n for (const [k, m] of state) {\n const used = Math.floor(k / stride);\n const r = k - used * stride;\n if (r === 0) {\n // No trials left; carry state forward unchanged\n const destKey = keyOf(used, 0);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr);\n next.set(destKey, dest);\n continue;\n }\n\n const bin = binomPMF(r, pCond);\n const remainingCapacity = keep - used;\n\n for (let x = 0; x <= r; x++) {\n const px = bin[x];\n if (px <= eps) continue;\n const t = Math.min(x, remainingCapacity);\n const used2 = used + t;\n const r2 = r - x;\n const add = t * v;\n\n const destKey = keyOf(used2, r2);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) {\n const s2 = sum + add;\n const prob = pr * px;\n const cur = dest.get(s2) || 0;\n const nv = cur + prob;\n if (nv >= eps) dest.set(s2, nv);\n }\n if (dest.size > 0) next.set(destKey, dest);\n }\n }\n\n // Light pruning proportional to eps\n state = pruneState(next, eps * 1e-6);\n processedMass += p;\n }\n\n // Collect results where all trials assigned and exactly keep were used\n const finalKey = keyOf(keep, 0);\n const dist = state.get(finalKey) ?? new Map();\n\n if (dist.size === 0) {\n // Fallback safety: return empty mass (should not happen)\n return PMF.emptyMass();\n }\n\n const result = PMF.fromMap(dist, eps);\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function getASTSignature(node: ExpressionNode): string {\n switch (node.type) {\n case \"constant\":\n return `c:${node.value}`;\n case \"die\": {\n // Use a fixed order for properties to ensure a stable signature.\n const parts: string[] = [];\n parts.push(`s:${node.sides}`);\n if (node.reroll) parts.push(`r:${node.reroll}`);\n if (node.minimum) parts.push(`m:${node.minimum}`);\n if (node.explode) parts.push(`e:${node.explode}`);\n return `d{${parts.join(\",\")}}`;\n }\n case \"sum\":\n return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"d20Roll\":\n return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`;\n case \"keep\":\n return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(\n node.child\n )}}`;\n case \"half\":\n return `half{ch:${getASTSignature(node.child)}}`;\n case \"maxOf\":\n return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"scale\":\n return `scale{n:${node.numerator},d:${node.denominator},r:${\n node.rounding\n },ch:${getASTSignature(node.child)}}`;\n case \"add\": {\n let constantValue = 0;\n const otherChildrenSigs: string[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n constantValue += c.sign * c.node.value;\n } else {\n otherChildrenSigs.push(\n `${c.sign === -1 ? \"-\" : \"+\"}${getASTSignature(c.node)}`\n );\n }\n }\n\n if (constantValue !== 0) {\n otherChildrenSigs.push(\n constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}`\n );\n }\n\n // Sort to handle commutative nature of addition.\n otherChildrenSigs.sort();\n\n return `add[${otherChildrenSigs.join(\"\")}]`;\n }\n }\n}\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport type { ACBuilder } from \"./ac\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport {\n AlwaysCritBuilder,\n AlwaysHitBuilder,\n ParsedRollBuilder,\n RollBuilder,\n} from \"./roll\";\nimport type { AttackResolution, CheckBuilder } from \"./types\";\n\ntype ActionEffect = RollBuilder;\n\nexport class AttackBuilder implements CheckBuilder {\n constructor(\n readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n private readonly hitEffect?: ActionEffect,\n private readonly critEffect?: ActionEffect | null,\n private readonly missEffect?: ActionEffect\n ) {}\n\n onCrit(val: number): AttackBuilder;\n onCrit(val: string): AttackBuilder;\n onCrit(val: RollBuilder): AttackBuilder;\n onCrit(count: number, die: RollBuilder): AttackBuilder;\n onCrit(count: number, sides: number): AttackBuilder;\n onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onCrit(count: number, sides: number, modifier: number): AttackBuilder;\n onCrit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n damageRoll,\n this.missEffect\n );\n }\n\n onMiss(val: number): AttackBuilder;\n onMiss(val: string): AttackBuilder;\n onMiss(val: RollBuilder): AttackBuilder;\n onMiss(count: number, die: RollBuilder): AttackBuilder;\n onMiss(count: number, sides: number): AttackBuilder;\n onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onMiss(count: number, sides: number, modifier: number): AttackBuilder;\n onMiss(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n this.critEffect,\n damageRoll\n );\n }\n\n noCrit(): AttackBuilder {\n return new AttackBuilder(this.check, this.hitEffect, null, this.missEffect);\n }\n\n // Legacy expressions\n toExpression(): string {\n const checkPart = this.check.toExpression();\n\n let effectPart = \"\";\n\n if (this.hitEffect) {\n effectPart = `(${this.hitEffect.toExpression()})`;\n if (this.critEffect !== null) {\n let crit: RollBuilder;\n if (this.critEffect) {\n crit = this.critEffect;\n } else {\n // For ParsedRollBuilder, we can't double dice, so skip the crit expression\n if (this.hitEffect instanceof ParsedRollBuilder) {\n // Don't try to double ParsedRollBuilder - leave it out of expression\n crit = RollBuilder.fromArgs(0);\n } else {\n crit =\n this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);\n }\n }\n\n const critThreshold = this.check.critThreshold;\n if (critThreshold < 1 || critThreshold > 20) {\n throw new Error(\n `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.`\n );\n }\n\n // Only include crit expression if crit is not zero\n const critExpression = crit.toExpression();\n if (critExpression !== \"0\") {\n if (critThreshold === 20) {\n effectPart += ` crit (${critExpression})`;\n } else {\n const xcritNumber = 21 - critThreshold;\n effectPart += ` xcrit${xcritNumber} (${critExpression})`;\n }\n }\n }\n\n if (this.missEffect) {\n effectPart += ` miss (${this.missEffect.toExpression()})`;\n }\n }\n\n return `${checkPart} * ${effectPart}`;\n }\n\n resolveProbabilities(\n check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n eps: number = 0\n ): { pSuccess: number; pHit: number; pCrit: number; pMiss: number } {\n const rollType = check.rollType;\n const rerollOne = check.baseReroll > 0;\n\n const critThreshold = check.critThreshold;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n if (check instanceof AlwaysCritBuilder) {\n // If fromAlwaysHit is true, everything is a crit (no misses)\n if (check.fromAlwaysHit) {\n return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };\n }\n\n // If fromAlwaysHit is false (came from ACBuilder), we need to check AC\n // Natural 1s always miss, everything else that would hit becomes a crit\n const ac = check.attackConfig.ac ?? 0;\n const staticMod = this.check.modifier;\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Natural 1 always misses\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Check if this roll would hit the AC\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n // Everything that hits becomes a crit\n pcrit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n return { pSuccess: pcrit, pHit: 0, pCrit: pcrit, pMiss: pmiss };\n }\n\n if (check instanceof AlwaysHitBuilder) {\n // Preserve rollType for crit odds\n let pCrit = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n if (r >= critThreshold) pCrit += pr;\n }\n const pHit = 1 - pCrit;\n const pMiss = 0;\n\n return { pSuccess: 1, pHit, pCrit, pMiss };\n }\n\n const ac = check.attackConfig.ac;\n const staticMod = this.check.modifier;\n\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let phit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Handle auto-miss\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Handle crit\n if (r >= critThreshold) {\n pcrit += pr;\n continue;\n }\n\n // Handle normal hit/miss\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n phit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n const psuccess = phit + pcrit;\n return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss };\n }\n\n resolve(eps: number = EPS): AttackResolution {\n const {\n pHit,\n pCrit,\n pMiss: pmiss,\n } = this.resolveProbabilities(this.check, eps);\n const hitPMF = this.hitEffect\n ? this.hitEffect instanceof ParsedRollBuilder\n ? this.hitEffect.toPMF(eps)\n : pmfFromRollBuilder(this.hitEffect, eps)\n : PMF.delta(0, eps);\n\n let critPMF: PMF | null = null;\n let phit = pHit;\n let pcrit = pCrit;\n\n if (this.critEffect === null) {\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n } else {\n let critBuilder: RollBuilder | undefined;\n \n if (this.critEffect) {\n critBuilder = this.critEffect;\n } else if (this.hitEffect instanceof ParsedRollBuilder) {\n // For ParsedRollBuilder, we can't automatically double dice\n // So treat it as noCrit() - roll crit probability into hit\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n critBuilder = undefined;\n } else {\n critBuilder = this.hitEffect?.copy().doubleDice();\n }\n\n if (critBuilder) {\n critPMF = critBuilder instanceof ParsedRollBuilder\n ? critBuilder.toPMF(eps)\n : pmfFromRollBuilder(critBuilder, eps);\n }\n }\n const missPMF = this.missEffect\n ? this.missEffect instanceof ParsedRollBuilder\n ? this.missEffect.toPMF(eps)\n : pmfFromRollBuilder(this.missEffect, eps)\n : PMF.delta(0, eps);\n\n // Mix them up\n const mix = new Mixture(eps);\n if (phit > 0) mix.add(\"hit\", hitPMF, phit);\n if (critPMF && pcrit > 0) mix.add(\"crit\", critPMF, pcrit);\n if (pmiss > 0)\n mix.add(this.missEffect ? \"missDamage\" : \"missNone\", missPMF, pmiss);\n\n return {\n pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps),\n check: this.check.toPMF(eps) ?? PMF.delta(0, eps),\n hit: hitPMF ?? PMF.delta(0, eps),\n crit: critPMF ?? PMF.delta(0, eps),\n miss: missPMF ?? PMF.delta(0, eps),\n weights: { hit: phit, crit: pcrit, miss: pmiss },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport { AlwaysCritBuilder, RollBuilder } from \"./roll\";\n\nexport interface AttackConfig {\n ac: number;\n critThreshold: number;\n}\nexport class ACBuilder extends RollBuilder {\n readonly attackConfig: AttackConfig;\n\n constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig) {\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig, ac };\n } else {\n this.attackConfig = { ac, critThreshold: 20 };\n }\n }\n\n // onHit(effect: RollBuilder): AttackBuilder {\n // return new AttackBuilder(this).onHit(effect)\n // }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(threshold: number): ACBuilder {\n const newConfig: AttackConfig = {\n ...this.attackConfig,\n critThreshold: threshold,\n };\n return new ACBuilder(this, this.attackConfig.ac, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(\n this,\n {\n critThreshold: this.attackConfig.critThreshold,\n ac: this.attackConfig.ac,\n },\n false\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs(); // This already includes bonus dice, no need to add them again\n const expression = new RollBuilder(configs).toExpression();\n return this.attackConfig.ac\n ? `(${expression} AC ${this.attackConfig.ac})`\n : expression;\n }\n\n override toPMF(eps: number = 0): PMF {\n const ac = this.attackConfig.ac;\n\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n const staticMod = this.modifier;\n const bonusPMFs = this.getBonusDicePMFs(this, eps);\n\n // Build total to-hit value distribution attackRollPMF = d20 ⊕ bonusDice, then shift by staticMod\n const parts = [d20, ...bonusPMFs];\n let attackRollPMF = parts.length === 1 ? d20 : PMF.convolveMany(parts, eps);\n if (staticMod !== 0)\n attackRollPMF = attackRollPMF.mapDamage(\n (rollValue) => rollValue + staticMod\n );\n\n // Map to 0 when below AC\n const out = new Map();\n for (const rollValue of attackRollPMF.support()) {\n const p = attackRollPMF.pAt(rollValue);\n const key = rollValue >= ac ? rollValue : 0;\n out.set(key, (out.get(key) || 0) + p);\n }\n return PMF.fromMap(out, eps);\n }\n\n override copy(): ACBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const newConfig = {\n ac: this.attackConfig.ac,\n critThreshold: this.attackConfig.critThreshold,\n };\n return new ACBuilder(baseCopy, newConfig.ac, newConfig);\n }\n}\n\n// Augment the RollBuilder prototype to implement the ac method\nRollBuilder.prototype.ac = function (targetAC: number): ACBuilder {\n if (isNaN(targetAC)) throw new Error(\"Invalid NaN value for targetAC\");\n return new ACBuilder(this, targetAC);\n};\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport type { DCBuilder } from \"./dc\";\nimport { ParsedRollBuilder, type RollBuilder } from \"./roll\";\nimport type { CheckBuilder, SaveResolution } from \"./types\";\n\nexport type SaveOutcome = \"normal\" | \"half\";\n\nexport class SaveBuilder implements CheckBuilder {\n constructor(\n readonly check: DCBuilder,\n private readonly failureEffect?: RollBuilder,\n private readonly saveOutcome: SaveOutcome = \"normal\"\n ) {}\n\n saveHalf(): SaveBuilder {\n return new SaveBuilder(this.check, this.failureEffect, \"half\");\n }\n\n toExpression(): string {\n const checkPart = this.check.toExpression();\n if (!this.failureEffect) return checkPart;\n\n const failureEffectPart = this.failureEffect.toExpression();\n const result = `${checkPart} * (${failureEffectPart})`;\n return this.saveOutcome === \"half\" ? `${result} save half` : result;\n }\n\n resolve(eps: number = EPS): SaveResolution {\n const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities(\n this.check\n );\n const failPMF = this.failureEffect\n ? this.failureEffect instanceof ParsedRollBuilder\n ? this.failureEffect.toPMF(eps)\n : pmfFromRollBuilder(this.failureEffect)\n : PMF.delta(0);\n const onSuccess = this.saveOutcome ?? \"half\";\n\n let successPMF: PMF = PMF.delta(0, eps);\n if (onSuccess === \"half\") successPMF = failPMF.scaleDamage(0.5, \"floor\");\n\n const successLabel: OutcomeType =\n onSuccess === \"normal\" ? \"missNone\" : \"saveHalf\";\n const failLabel: OutcomeType = \"saveFail\";\n const baseMix = new Mixture(eps);\n const mixture = baseMix\n .add(successLabel, successPMF, psuccess)\n .add(failLabel, failPMF, pfail);\n\n return {\n pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps),\n check:\n PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps),\n saveFail: failPMF ?? PMF.delta(0, eps),\n saveSuccess: successPMF ?? PMF.delta(0, eps),\n weights: { success: psuccess, fail: pfail },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n\nfunction resolveProbabilities(check: DCBuilder): {\n pSuccess: number;\n pFail: number;\n} {\n const saveBonus = check.modifier;\n const dc = check.saveDC;\n const d20Type = check.rollType;\n const baseReroll = check.baseReroll;\n // TODO later check if base reroll is not 0 or 1.\n\n const die = d20RollPMF(d20Type, baseReroll > 0);\n const faceP = new Map();\n for (const [r, bin] of die) {\n const pr = bin.p;\n if (pr > 0) faceP.set(r, pr);\n }\n\n // Now add bonus dice to the PMF (bless, bane, bardic, etc)\n const eps = 0;\n const bonusDicePMFs = check.getBonusDicePMFs(check, eps);\n const bonusPMF =\n bonusDicePMFs.length > 0\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.zero(eps);\n\n let pSuccess = 0;\n for (let r = 1; r <= 20; r++) {\n const pr = faceP.get(r);\n if (!pr) continue;\n const need = dc - saveBonus - r;\n pSuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pFail = Math.max(0, 1 - pSuccess);\n return { pSuccess, pFail: pFail };\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport { RollBuilder } from \"./roll\";\nimport { SaveBuilder } from \"./save\";\n\ninterface SaveConfig {\n dc: number;\n}\n\nexport class DCBuilder extends RollBuilder {\n private readonly saveConfig: SaveConfig;\n\n constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig) {\n super(baseRoll.getSubRollConfigs());\n this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 };\n }\n\n override dc(saveDC: number): DCBuilder {\n if (this.rollType && this.rollType === \"elven accuracy\") {\n throw new Error(\n \"Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead.\"\n );\n }\n return new DCBuilder(this, { dc: saveDC });\n }\n\n get saveDC(): number {\n return this.saveConfig.dc;\n }\n\n override add(anotherRoll: RollBuilder): DCBuilder {\n const newBuilder = super.add(anotherRoll);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n override addRoll(count?: number): DCBuilder {\n const newBuilder = super.addRoll(count);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n onSaveFailure(val: number): SaveBuilder;\n onSaveFailure(val: string): SaveBuilder;\n onSaveFailure(val: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, sides: number): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder;\n onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder;\n onSaveFailure(...args: any[]): SaveBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new SaveBuilder(this, damageRoll);\n }\n\n override withElvenAccuracy(): never {\n throw new Error(\n \"Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks).\"\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const subConfigs = this.getSubRollConfigs();\n const allConfigs = [...subConfigs];\n const expression = new RollBuilder(allConfigs).toExpression();\n return `(${expression} DC ${this.saveConfig.dc})`;\n }\n\n override toPMF(eps: number = 0): PMF {\n const saveDC = this.saveDC;\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n const staticMod = this.modifier;\n const bonusDicePMFs = this.getBonusDiceConfigs().map((cfg) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps)\n );\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let psuccess = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n const need = saveDC - staticMod - r;\n psuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pfail = Math.max(0, 1 - psuccess);\n const m = new Map([\n [0, psuccess > 0 ? psuccess : 0],\n [1, pfail > 0 ? pfail : 0],\n ]);\n return PMF.fromMap(m, eps);\n }\n}\n\n// Augment the RollBuilder prototype to implement the dc method\nRollBuilder.prototype.dc = function (saveDC: number): DCBuilder {\n if (isNaN(saveDC)) throw new Error(\"Invalid NaN value for saveDC\");\n return new DCBuilder(this).dc(saveDC);\n};\n"]} \ No newline at end of file diff --git a/dist/builder/index.d.cts b/dist/builder/index.d.cts deleted file mode 100644 index 3c21b41..0000000 --- a/dist/builder/index.d.cts +++ /dev/null @@ -1,429 +0,0 @@ -import { b as RollType, P as PMF, l as DiceQuery, C as CritConfig, L as LRUCache } from '../pmf-D5VRghZI.cjs'; - -type RollFactory = { - (count: number, sides?: number, modifier?: number): RollBuilder; - (count: number, die: RollBuilder, modifier?: number): RollBuilder; - d(sides: number | string): RollBuilder; - hd20(): RollBuilder; - d4(): RollBuilder; - d6(): RollBuilder; - d8(): RollBuilder; - d10(): RollBuilder; - d12(): RollBuilder; - d20(): RollBuilder; - d100(): RollBuilder; - flat(n: number): RollBuilder; -}; -type KeepMode = "highest" | "lowest"; -type RollConfig = { - count: number; - sides: number; - modifier: number; - reroll: number; - explode: number; - minimum: number; - bestOf: number; - keep: { - total: number; - count: number; - mode: KeepMode; - } | undefined; - rollType: RollType; - isSubtraction?: boolean; -}; -type Resolution = { - pmf: PMF; - check: PMF; - weights: { - [key: string]: number; - }; -}; -type AttackResolution = Resolution & { - hit: PMF; - crit: PMF; - miss: PMF; - weights: { - hit: number; - crit: number; - miss: number; - }; -}; -type SaveResolution = Resolution & { - saveFail: PMF; - saveSuccess: PMF; - weights: { - success: number; - fail: number; - }; -}; -interface CheckBuilder { - resolve(eps?: number): Resolution; - toExpression(): string; - readonly pmf: PMF; -} - -type SaveOutcome = "normal" | "half"; -declare class SaveBuilder implements CheckBuilder { - readonly check: DCBuilder; - private readonly failureEffect?; - private readonly saveOutcome; - constructor(check: DCBuilder, failureEffect?: RollBuilder | undefined, saveOutcome?: SaveOutcome); - saveHalf(): SaveBuilder; - toExpression(): string; - resolve(eps?: number): SaveResolution; - toPMF(eps?: number): PMF; - get pmf(): PMF; - toQuery(eps?: number): DiceQuery; -} - -interface SaveConfig { - dc: number; -} -declare class DCBuilder extends RollBuilder { - private readonly saveConfig; - constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig); - dc(saveDC: number): DCBuilder; - get saveDC(): number; - add(anotherRoll: RollBuilder): DCBuilder; - addRoll(count?: number): DCBuilder; - onSaveFailure(val: number): SaveBuilder; - onSaveFailure(val: string): SaveBuilder; - onSaveFailure(val: RollBuilder): SaveBuilder; - onSaveFailure(count: number, die: RollBuilder): SaveBuilder; - onSaveFailure(count: number, sides: number): SaveBuilder; - onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder; - onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder; - withElvenAccuracy(): never; - toExpression(): string; - toPMF(eps?: number): PMF; -} - -type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode | ScaleNode; -type DieNode = { - type: "die"; - sides: number; - reroll?: number; - minimum?: number; - explode?: number; -}; -type ConstantNode = { - type: "constant"; - value: number; -}; -type SumNode = { - type: "sum"; - count: number; - child: ExpressionNode; -}; -type AddNode = { - type: "add"; - children: { - node: ExpressionNode; - sign: 1 | -1; - }[]; -}; -type KeepNode = { - type: "keep"; - mode: "highest" | "lowest"; - count: number; - child: SumNode | KeepNode; -}; -type D20RollNode = { - type: "d20Roll"; - rollType: "advantage" | "disadvantage" | "elven accuracy"; - child: ExpressionNode; -}; -type HalfNode = { - type: "half"; - child: ExpressionNode; -}; -type MaxOfNode = { - type: "maxOf"; - count: number; - child: ExpressionNode; -}; -/** - * Scale the child's result by `numerator / denominator`, then round. - * - * Unlike {@link HalfNode} (a fixed `// 2` with floor), this is a general, composable - * multiplier/divider — the building block for damage-type resistance (`1/2`, floor), - * vulnerability (`2/1`), and similar per-source transforms. It renders as - * `N * (child)` when the denominator is 1, `(child) // D` when the numerator is 1, - * and `(child) * N // D` otherwise. - */ -type ScaleNode = { - type: "scale"; - numerator: number; - denominator: number; - rounding: "floor" | "round" | "ceil"; - child: ExpressionNode; -}; - -declare const defaultConfig: RollConfig; -declare class RollBuilder { - protected readonly subRollConfigs: readonly RollConfig[]; - constructor(countOrConfigs?: number | readonly RollConfig[]); - protected create(configs: readonly RollConfig[]): RollBuilder; - protected get lastConfig(): RollConfig; - hasHiddenState(): boolean; - getSubRollConfigs(): readonly RollConfig[]; - static fromConfig(config: Partial): RollBuilder; - static fromConfigs(configs: Partial[]): RollBuilder; - static fromArgs(...args: any[]): RollBuilder; - d(sides: number | undefined): RollBuilder; - plus(modOrRoll: number | RollBuilder | undefined): RollBuilder; - plus(count: number, die: RollBuilder): RollBuilder; - minus(modOrRoll: number | RollBuilder | undefined): RollBuilder; - minus(count: number, die: RollBuilder): RollBuilder; - /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ - reroll(value: number): RollBuilder; - /** Set finite explode count for max-face explosions (Infinity allowed). */ - explode(count?: number | undefined): RollBuilder; - /** Apply per-die minimum value (min > 0). */ - minimum(val: number | undefined): RollBuilder; - bestOf(count: number | undefined): RollBuilder; - keepHighest(total: number, count: number): RollBuilder; - keepLowest(total: number, count: number): RollBuilder; - keepHighestAll(total: number, count: number): PooledRollBuilder; - keepLowestAll(total: number, count: number): PooledRollBuilder; - withAdvantage(): RollBuilder; - withDisadvantage(): RollBuilder; - add(anotherRoll: RollBuilder | undefined): RollBuilder; - withBonus(anotherRoll: RollBuilder): RollBuilder; - addRoll(count?: number): RollBuilder; - scaleDice(scale: number): RollBuilder; - doubleDice(): RollBuilder; - alwaysHits(): AlwaysHitBuilder; - alwaysCrits(): AlwaysCritBuilder; - copy(): RollBuilder; - d4: () => RollBuilder; - d6: () => RollBuilder; - d8: () => RollBuilder; - d10: () => RollBuilder; - d12: () => RollBuilder; - d20: () => RollBuilder; - d100: () => RollBuilder; - withElvenAccuracy(): RollBuilder; - toExpression(): string; - toPMF(eps?: number): PMF; - get pmf(): PMF; - toQuery(eps?: number): DiceQuery; - toAST(): ExpressionNode; - private configToSingleExpressionWithoutModifier; - getRootDieConfig(): RollConfig | undefined; - getAllDieConfigs(): readonly RollConfig[]; - getBonusDiceConfigs(): RollConfig[]; - getBonusDicePMFs(check: RollBuilder, eps?: number): PMF[]; - get modifier(): number; - get rollType(): RollType; - get baseReroll(): number; - half(): HalfRollBuilder; - /** - * Scale this roll's result by `numerator / denominator`, rounding each outcome. - * A general, composable form of {@link half} — used to model damage-type resistance - * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). - * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. - */ - scaleResult(numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"): ScaleRollBuilder; - maxOf(count: number): MaxOfRollBuilder; - ac(_targetAC: number): ACBuilder; - dc(_saveDC: number): DCBuilder; -} -declare class HalfRollBuilder extends RollBuilder { - private readonly innerRoll; - constructor(innerRoll: RollBuilder); - hasHiddenState(): boolean; - get lastConfig(): RollConfig; - getSubRollConfigs(): readonly RollConfig[]; - toExpression(): string; - toAST(): ExpressionNode; - toPMF(eps?: number): PMF; - copy(): HalfRollBuilder; -} -/** - * A roll whose result is scaled by `numerator / denominator` and rounded — the composable - * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or - * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls. - */ -declare class ScaleRollBuilder extends RollBuilder { - private readonly innerRoll; - private readonly numerator; - private readonly denominator; - private readonly rounding; - constructor(innerRoll: RollBuilder, numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"); - hasHiddenState(): boolean; - get lastConfig(): RollConfig; - getSubRollConfigs(): readonly RollConfig[]; - toExpression(): string; - toAST(): ExpressionNode; - toPMF(eps?: number): PMF; - copy(): ScaleRollBuilder; -} -declare class MaxOfRollBuilder extends RollBuilder { - private readonly innerRoll; - private readonly count; - private readonly diceCount?; - private readonly diceSides?; - constructor(innerRoll: RollBuilder, count: number, diceCount?: number | undefined, diceSides?: number | undefined); - hasHiddenState(): boolean; - get lastConfig(): RollConfig; - getSubRollConfigs(): readonly RollConfig[]; - toExpression(): string; - toAST(): ExpressionNode; - toPMF(eps?: number): PMF; - copy(): MaxOfRollBuilder; -} -declare class AlwaysHitBuilder extends RollBuilder { - readonly attackConfig: CritConfig; - constructor(baseRoll: RollBuilder, attackConfig?: CritConfig); - protected create(configs: readonly RollConfig[]): RollBuilder; - onHit(val: number): AttackBuilder; - onHit(val: string): AttackBuilder; - onHit(val: RollBuilder): AttackBuilder; - onHit(count: number, die: RollBuilder): AttackBuilder; - onHit(count: number, sides: number): AttackBuilder; - onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onHit(count: number, sides: number, modifier: number): AttackBuilder; - get critThreshold(): number; - critOn(critThreshold: number): AlwaysHitBuilder; - alwaysCrits(): AlwaysCritBuilder; - toExpression(): string; - toPMF(): PMF; - copy(): AlwaysHitBuilder; -} -declare class AlwaysCritBuilder extends RollBuilder { - readonly attackConfig: CritConfig & { - ac?: number; - }; - readonly fromAlwaysHit: boolean; - constructor(baseRoll: RollBuilder, attackConfig?: CritConfig & { - ac?: number; - }, fromAlwaysHit?: boolean); - protected create(configs: readonly RollConfig[]): RollBuilder; - onHit(val: number): AttackBuilder; - onHit(val: string): AttackBuilder; - onHit(val: RollBuilder): AttackBuilder; - onHit(count: number, die: RollBuilder): AttackBuilder; - onHit(count: number, sides: number): AttackBuilder; - onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onHit(count: number, sides: number, modifier: number): AttackBuilder; - get critThreshold(): number; - critOn(critThreshold: number): AlwaysCritBuilder; - toExpression(): string; - toPMF(): PMF; - copy(): AlwaysCritBuilder; -} -declare class ParsedRollBuilder extends RollBuilder { - private readonly cachedPMF; - private readonly originalExpression; - constructor(expression: string); - hasHiddenState(): boolean; - protected create(configs: readonly RollConfig[]): RollBuilder; - toPMF(_eps?: number): PMF; - toExpression(): string; - toAST(): ExpressionNode; - copy(): ParsedRollBuilder; - doubleDice(): ParsedRollBuilder; -} -declare class PooledRollBuilder extends RollBuilder { - private readonly baseAST; - private readonly baseExpression; - constructor(baseAST: ExpressionNode, baseExpression: string, configs?: readonly RollConfig[]); - protected create(configs: readonly RollConfig[]): PooledRollBuilder; - hasHiddenState(): boolean; - d(_sides: number | undefined): RollBuilder; - reroll(_value: number): RollBuilder; - explode(_count?: number | undefined): RollBuilder; - minimum(_val: number | undefined): RollBuilder; - bestOf(_count: number | undefined): RollBuilder; - keepHighest(_total: number, _count: number): RollBuilder; - keepLowest(_total: number, _count: number): RollBuilder; - withAdvantage(): RollBuilder; - withDisadvantage(): RollBuilder; - withElvenAccuracy(): RollBuilder; - toAST(): ExpressionNode; - toExpression(): string; - copy(): PooledRollBuilder; - scaleDice(scale: number): RollBuilder; - times(count: number): PooledRollBuilder; -} -/** - * Combine several rolls into one additive payload whose PMF is their convolution and whose - * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry - * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance - * and vulnerability survive into both the distribution and the rendered expression. - * Empty parts collapse to `0`; a single part is returned unwrapped. - */ -declare function sumRolls(parts: readonly RollBuilder[]): RollBuilder; - -type ActionEffect = RollBuilder; -declare class AttackBuilder implements CheckBuilder { - readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder; - private readonly hitEffect?; - private readonly critEffect?; - private readonly missEffect?; - constructor(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, hitEffect?: ActionEffect | undefined, critEffect?: (ActionEffect | null) | undefined, missEffect?: ActionEffect | undefined); - onCrit(val: number): AttackBuilder; - onCrit(val: string): AttackBuilder; - onCrit(val: RollBuilder): AttackBuilder; - onCrit(count: number, die: RollBuilder): AttackBuilder; - onCrit(count: number, sides: number): AttackBuilder; - onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onCrit(count: number, sides: number, modifier: number): AttackBuilder; - onMiss(val: number): AttackBuilder; - onMiss(val: string): AttackBuilder; - onMiss(val: RollBuilder): AttackBuilder; - onMiss(count: number, die: RollBuilder): AttackBuilder; - onMiss(count: number, sides: number): AttackBuilder; - onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onMiss(count: number, sides: number, modifier: number): AttackBuilder; - noCrit(): AttackBuilder; - toExpression(): string; - resolveProbabilities(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, eps?: number): { - pSuccess: number; - pHit: number; - pCrit: number; - pMiss: number; - }; - resolve(eps?: number): AttackResolution; - toPMF(eps?: number): PMF; - get pmf(): PMF; - toQuery(eps?: number): DiceQuery; -} - -interface AttackConfig { - ac: number; - critThreshold: number; -} -declare class ACBuilder extends RollBuilder { - readonly attackConfig: AttackConfig; - constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig); - onHit(val: number): AttackBuilder; - onHit(val: string): AttackBuilder; - onHit(val: RollBuilder): AttackBuilder; - onHit(count: number, die: RollBuilder): AttackBuilder; - onHit(count: number, sides: number): AttackBuilder; - onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onHit(count: number, sides: number, modifier: number): AttackBuilder; - get critThreshold(): number; - critOn(threshold: number): ACBuilder; - alwaysCrits(): AlwaysCritBuilder; - toExpression(): string; - toPMF(eps?: number): PMF; - copy(): ACBuilder; -} - -declare function d(sides: number | string): RollBuilder; -declare const d4: RollBuilder; -declare const d6: RollBuilder; -declare const d8: RollBuilder; -declare const d10: RollBuilder; -declare const d12: RollBuilder; -declare const d20: RollBuilder; -declare const hd20: RollBuilder; -declare const d100: RollBuilder; -declare const flat: (n: number) => RollBuilder; -declare const roll: RollFactory; -declare const builderPMFCache: LRUCache; - -export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, RollType, SaveBuilder, type SaveOutcome, type SaveResolution, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls }; diff --git a/dist/builder/index.d.ts b/dist/builder/index.d.ts deleted file mode 100644 index 6a898cf..0000000 --- a/dist/builder/index.d.ts +++ /dev/null @@ -1,429 +0,0 @@ -import { b as RollType, P as PMF, l as DiceQuery, C as CritConfig, L as LRUCache } from '../pmf-D5VRghZI.js'; - -type RollFactory = { - (count: number, sides?: number, modifier?: number): RollBuilder; - (count: number, die: RollBuilder, modifier?: number): RollBuilder; - d(sides: number | string): RollBuilder; - hd20(): RollBuilder; - d4(): RollBuilder; - d6(): RollBuilder; - d8(): RollBuilder; - d10(): RollBuilder; - d12(): RollBuilder; - d20(): RollBuilder; - d100(): RollBuilder; - flat(n: number): RollBuilder; -}; -type KeepMode = "highest" | "lowest"; -type RollConfig = { - count: number; - sides: number; - modifier: number; - reroll: number; - explode: number; - minimum: number; - bestOf: number; - keep: { - total: number; - count: number; - mode: KeepMode; - } | undefined; - rollType: RollType; - isSubtraction?: boolean; -}; -type Resolution = { - pmf: PMF; - check: PMF; - weights: { - [key: string]: number; - }; -}; -type AttackResolution = Resolution & { - hit: PMF; - crit: PMF; - miss: PMF; - weights: { - hit: number; - crit: number; - miss: number; - }; -}; -type SaveResolution = Resolution & { - saveFail: PMF; - saveSuccess: PMF; - weights: { - success: number; - fail: number; - }; -}; -interface CheckBuilder { - resolve(eps?: number): Resolution; - toExpression(): string; - readonly pmf: PMF; -} - -type SaveOutcome = "normal" | "half"; -declare class SaveBuilder implements CheckBuilder { - readonly check: DCBuilder; - private readonly failureEffect?; - private readonly saveOutcome; - constructor(check: DCBuilder, failureEffect?: RollBuilder | undefined, saveOutcome?: SaveOutcome); - saveHalf(): SaveBuilder; - toExpression(): string; - resolve(eps?: number): SaveResolution; - toPMF(eps?: number): PMF; - get pmf(): PMF; - toQuery(eps?: number): DiceQuery; -} - -interface SaveConfig { - dc: number; -} -declare class DCBuilder extends RollBuilder { - private readonly saveConfig; - constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig); - dc(saveDC: number): DCBuilder; - get saveDC(): number; - add(anotherRoll: RollBuilder): DCBuilder; - addRoll(count?: number): DCBuilder; - onSaveFailure(val: number): SaveBuilder; - onSaveFailure(val: string): SaveBuilder; - onSaveFailure(val: RollBuilder): SaveBuilder; - onSaveFailure(count: number, die: RollBuilder): SaveBuilder; - onSaveFailure(count: number, sides: number): SaveBuilder; - onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder; - onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder; - withElvenAccuracy(): never; - toExpression(): string; - toPMF(eps?: number): PMF; -} - -type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode | ScaleNode; -type DieNode = { - type: "die"; - sides: number; - reroll?: number; - minimum?: number; - explode?: number; -}; -type ConstantNode = { - type: "constant"; - value: number; -}; -type SumNode = { - type: "sum"; - count: number; - child: ExpressionNode; -}; -type AddNode = { - type: "add"; - children: { - node: ExpressionNode; - sign: 1 | -1; - }[]; -}; -type KeepNode = { - type: "keep"; - mode: "highest" | "lowest"; - count: number; - child: SumNode | KeepNode; -}; -type D20RollNode = { - type: "d20Roll"; - rollType: "advantage" | "disadvantage" | "elven accuracy"; - child: ExpressionNode; -}; -type HalfNode = { - type: "half"; - child: ExpressionNode; -}; -type MaxOfNode = { - type: "maxOf"; - count: number; - child: ExpressionNode; -}; -/** - * Scale the child's result by `numerator / denominator`, then round. - * - * Unlike {@link HalfNode} (a fixed `// 2` with floor), this is a general, composable - * multiplier/divider — the building block for damage-type resistance (`1/2`, floor), - * vulnerability (`2/1`), and similar per-source transforms. It renders as - * `N * (child)` when the denominator is 1, `(child) // D` when the numerator is 1, - * and `(child) * N // D` otherwise. - */ -type ScaleNode = { - type: "scale"; - numerator: number; - denominator: number; - rounding: "floor" | "round" | "ceil"; - child: ExpressionNode; -}; - -declare const defaultConfig: RollConfig; -declare class RollBuilder { - protected readonly subRollConfigs: readonly RollConfig[]; - constructor(countOrConfigs?: number | readonly RollConfig[]); - protected create(configs: readonly RollConfig[]): RollBuilder; - protected get lastConfig(): RollConfig; - hasHiddenState(): boolean; - getSubRollConfigs(): readonly RollConfig[]; - static fromConfig(config: Partial): RollBuilder; - static fromConfigs(configs: Partial[]): RollBuilder; - static fromArgs(...args: any[]): RollBuilder; - d(sides: number | undefined): RollBuilder; - plus(modOrRoll: number | RollBuilder | undefined): RollBuilder; - plus(count: number, die: RollBuilder): RollBuilder; - minus(modOrRoll: number | RollBuilder | undefined): RollBuilder; - minus(count: number, die: RollBuilder): RollBuilder; - /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ - reroll(value: number): RollBuilder; - /** Set finite explode count for max-face explosions (Infinity allowed). */ - explode(count?: number | undefined): RollBuilder; - /** Apply per-die minimum value (min > 0). */ - minimum(val: number | undefined): RollBuilder; - bestOf(count: number | undefined): RollBuilder; - keepHighest(total: number, count: number): RollBuilder; - keepLowest(total: number, count: number): RollBuilder; - keepHighestAll(total: number, count: number): PooledRollBuilder; - keepLowestAll(total: number, count: number): PooledRollBuilder; - withAdvantage(): RollBuilder; - withDisadvantage(): RollBuilder; - add(anotherRoll: RollBuilder | undefined): RollBuilder; - withBonus(anotherRoll: RollBuilder): RollBuilder; - addRoll(count?: number): RollBuilder; - scaleDice(scale: number): RollBuilder; - doubleDice(): RollBuilder; - alwaysHits(): AlwaysHitBuilder; - alwaysCrits(): AlwaysCritBuilder; - copy(): RollBuilder; - d4: () => RollBuilder; - d6: () => RollBuilder; - d8: () => RollBuilder; - d10: () => RollBuilder; - d12: () => RollBuilder; - d20: () => RollBuilder; - d100: () => RollBuilder; - withElvenAccuracy(): RollBuilder; - toExpression(): string; - toPMF(eps?: number): PMF; - get pmf(): PMF; - toQuery(eps?: number): DiceQuery; - toAST(): ExpressionNode; - private configToSingleExpressionWithoutModifier; - getRootDieConfig(): RollConfig | undefined; - getAllDieConfigs(): readonly RollConfig[]; - getBonusDiceConfigs(): RollConfig[]; - getBonusDicePMFs(check: RollBuilder, eps?: number): PMF[]; - get modifier(): number; - get rollType(): RollType; - get baseReroll(): number; - half(): HalfRollBuilder; - /** - * Scale this roll's result by `numerator / denominator`, rounding each outcome. - * A general, composable form of {@link half} — used to model damage-type resistance - * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). - * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. - */ - scaleResult(numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"): ScaleRollBuilder; - maxOf(count: number): MaxOfRollBuilder; - ac(_targetAC: number): ACBuilder; - dc(_saveDC: number): DCBuilder; -} -declare class HalfRollBuilder extends RollBuilder { - private readonly innerRoll; - constructor(innerRoll: RollBuilder); - hasHiddenState(): boolean; - get lastConfig(): RollConfig; - getSubRollConfigs(): readonly RollConfig[]; - toExpression(): string; - toAST(): ExpressionNode; - toPMF(eps?: number): PMF; - copy(): HalfRollBuilder; -} -/** - * A roll whose result is scaled by `numerator / denominator` and rounded — the composable - * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or - * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls. - */ -declare class ScaleRollBuilder extends RollBuilder { - private readonly innerRoll; - private readonly numerator; - private readonly denominator; - private readonly rounding; - constructor(innerRoll: RollBuilder, numerator: number, denominator?: number, rounding?: "floor" | "round" | "ceil"); - hasHiddenState(): boolean; - get lastConfig(): RollConfig; - getSubRollConfigs(): readonly RollConfig[]; - toExpression(): string; - toAST(): ExpressionNode; - toPMF(eps?: number): PMF; - copy(): ScaleRollBuilder; -} -declare class MaxOfRollBuilder extends RollBuilder { - private readonly innerRoll; - private readonly count; - private readonly diceCount?; - private readonly diceSides?; - constructor(innerRoll: RollBuilder, count: number, diceCount?: number | undefined, diceSides?: number | undefined); - hasHiddenState(): boolean; - get lastConfig(): RollConfig; - getSubRollConfigs(): readonly RollConfig[]; - toExpression(): string; - toAST(): ExpressionNode; - toPMF(eps?: number): PMF; - copy(): MaxOfRollBuilder; -} -declare class AlwaysHitBuilder extends RollBuilder { - readonly attackConfig: CritConfig; - constructor(baseRoll: RollBuilder, attackConfig?: CritConfig); - protected create(configs: readonly RollConfig[]): RollBuilder; - onHit(val: number): AttackBuilder; - onHit(val: string): AttackBuilder; - onHit(val: RollBuilder): AttackBuilder; - onHit(count: number, die: RollBuilder): AttackBuilder; - onHit(count: number, sides: number): AttackBuilder; - onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onHit(count: number, sides: number, modifier: number): AttackBuilder; - get critThreshold(): number; - critOn(critThreshold: number): AlwaysHitBuilder; - alwaysCrits(): AlwaysCritBuilder; - toExpression(): string; - toPMF(): PMF; - copy(): AlwaysHitBuilder; -} -declare class AlwaysCritBuilder extends RollBuilder { - readonly attackConfig: CritConfig & { - ac?: number; - }; - readonly fromAlwaysHit: boolean; - constructor(baseRoll: RollBuilder, attackConfig?: CritConfig & { - ac?: number; - }, fromAlwaysHit?: boolean); - protected create(configs: readonly RollConfig[]): RollBuilder; - onHit(val: number): AttackBuilder; - onHit(val: string): AttackBuilder; - onHit(val: RollBuilder): AttackBuilder; - onHit(count: number, die: RollBuilder): AttackBuilder; - onHit(count: number, sides: number): AttackBuilder; - onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onHit(count: number, sides: number, modifier: number): AttackBuilder; - get critThreshold(): number; - critOn(critThreshold: number): AlwaysCritBuilder; - toExpression(): string; - toPMF(): PMF; - copy(): AlwaysCritBuilder; -} -declare class ParsedRollBuilder extends RollBuilder { - private readonly cachedPMF; - private readonly originalExpression; - constructor(expression: string); - hasHiddenState(): boolean; - protected create(configs: readonly RollConfig[]): RollBuilder; - toPMF(_eps?: number): PMF; - toExpression(): string; - toAST(): ExpressionNode; - copy(): ParsedRollBuilder; - doubleDice(): ParsedRollBuilder; -} -declare class PooledRollBuilder extends RollBuilder { - private readonly baseAST; - private readonly baseExpression; - constructor(baseAST: ExpressionNode, baseExpression: string, configs?: readonly RollConfig[]); - protected create(configs: readonly RollConfig[]): PooledRollBuilder; - hasHiddenState(): boolean; - d(_sides: number | undefined): RollBuilder; - reroll(_value: number): RollBuilder; - explode(_count?: number | undefined): RollBuilder; - minimum(_val: number | undefined): RollBuilder; - bestOf(_count: number | undefined): RollBuilder; - keepHighest(_total: number, _count: number): RollBuilder; - keepLowest(_total: number, _count: number): RollBuilder; - withAdvantage(): RollBuilder; - withDisadvantage(): RollBuilder; - withElvenAccuracy(): RollBuilder; - toAST(): ExpressionNode; - toExpression(): string; - copy(): PooledRollBuilder; - scaleDice(scale: number): RollBuilder; - times(count: number): PooledRollBuilder; -} -/** - * Combine several rolls into one additive payload whose PMF is their convolution and whose - * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry - * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance - * and vulnerability survive into both the distribution and the rendered expression. - * Empty parts collapse to `0`; a single part is returned unwrapped. - */ -declare function sumRolls(parts: readonly RollBuilder[]): RollBuilder; - -type ActionEffect = RollBuilder; -declare class AttackBuilder implements CheckBuilder { - readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder; - private readonly hitEffect?; - private readonly critEffect?; - private readonly missEffect?; - constructor(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, hitEffect?: ActionEffect | undefined, critEffect?: (ActionEffect | null) | undefined, missEffect?: ActionEffect | undefined); - onCrit(val: number): AttackBuilder; - onCrit(val: string): AttackBuilder; - onCrit(val: RollBuilder): AttackBuilder; - onCrit(count: number, die: RollBuilder): AttackBuilder; - onCrit(count: number, sides: number): AttackBuilder; - onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onCrit(count: number, sides: number, modifier: number): AttackBuilder; - onMiss(val: number): AttackBuilder; - onMiss(val: string): AttackBuilder; - onMiss(val: RollBuilder): AttackBuilder; - onMiss(count: number, die: RollBuilder): AttackBuilder; - onMiss(count: number, sides: number): AttackBuilder; - onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onMiss(count: number, sides: number, modifier: number): AttackBuilder; - noCrit(): AttackBuilder; - toExpression(): string; - resolveProbabilities(check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder, eps?: number): { - pSuccess: number; - pHit: number; - pCrit: number; - pMiss: number; - }; - resolve(eps?: number): AttackResolution; - toPMF(eps?: number): PMF; - get pmf(): PMF; - toQuery(eps?: number): DiceQuery; -} - -interface AttackConfig { - ac: number; - critThreshold: number; -} -declare class ACBuilder extends RollBuilder { - readonly attackConfig: AttackConfig; - constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig); - onHit(val: number): AttackBuilder; - onHit(val: string): AttackBuilder; - onHit(val: RollBuilder): AttackBuilder; - onHit(count: number, die: RollBuilder): AttackBuilder; - onHit(count: number, sides: number): AttackBuilder; - onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder; - onHit(count: number, sides: number, modifier: number): AttackBuilder; - get critThreshold(): number; - critOn(threshold: number): ACBuilder; - alwaysCrits(): AlwaysCritBuilder; - toExpression(): string; - toPMF(eps?: number): PMF; - copy(): ACBuilder; -} - -declare function d(sides: number | string): RollBuilder; -declare const d4: RollBuilder; -declare const d6: RollBuilder; -declare const d8: RollBuilder; -declare const d10: RollBuilder; -declare const d12: RollBuilder; -declare const d20: RollBuilder; -declare const hd20: RollBuilder; -declare const d100: RollBuilder; -declare const flat: (n: number) => RollBuilder; -declare const roll: RollFactory; -declare const builderPMFCache: LRUCache; - -export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, RollType, SaveBuilder, type SaveOutcome, type SaveResolution, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls }; diff --git a/dist/builder/index.js b/dist/builder/index.js deleted file mode 100644 index bf9fda0..0000000 --- a/dist/builder/index.js +++ /dev/null @@ -1,5474 +0,0 @@ -// src/common/lru-cache.ts -var LRUCache = class { - constructor(maxSize = 1e3) { - this.maxSize = maxSize; - this.cache = /* @__PURE__ */ new Map(); - } - get(key) { - const value = this.cache.get(key); - if (value === void 0) return void 0; - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - delete(key) { - this.cache.delete(key); - } - set(key, value) { - if (this.cache.size >= this.maxSize && !this.cache.has(key)) { - const oldestKey = this.cache.keys().next().value; - this.cache.delete(oldestKey); - } - this.cache.delete(key); - this.cache.set(key, value); - return this; - } - clear() { - this.cache.clear(); - } - get size() { - return this.cache.size; - } - has(key) { - return this.cache.has(key); - } - keys() { - return this.cache.keys(); - } - values() { - return this.cache.values(); - } -}; - -// src/common/types.ts -var EPS = 1e-12; -var MISS_NONE_OUTCOME = "missNone"; - -// src/pmf/query.ts -var _DiceQuery = class _DiceQuery { - constructor(singles, combined, eps = EPS) { - this.singles = Array.isArray(singles) ? singles : [singles]; - if (this.singles.some((s) => s === void 0)) { - throw new Error("DiceQuery contains undefined singles"); - } - this._eps = eps; - this._combinedProvided = combined !== void 0; - if (combined !== void 0) { - this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); - } - } - /** - * The combined damage distribution of all single PMFs (their convolution), - * normalized to total probability 1. - * - * Computed lazily on first access and cached. Queries that only need - * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, - * {@link DiceQuery.stddev} — never trigger this convolution. - */ - get combined() { - if (this._combined === void 0) { - const c = PMF.convolveMany(this.singles); - this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); - } - return this._combined; - } - /** - * Returns a new PMF with damage attribution metadata populated. - * - * This method computes attribution on-demand for builder-generated PMFs, - * enabling them to work with damage attribution charts. The `attr` field - * tracks how much damage each outcome type contributes at each damage value. - * - * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) - * - * Performance: Cached after first call. Adds minimal overhead vs `combined`. - * - * @returns PMF with attr field populated for damage attribution charts - * - * @example - * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) - * const query = attack.toQuery() - * const pmf = query.combinedWithAttribution() - * // Now pmf can be used with toDamageAttributionChartSeries() - */ - combinedWithAttribution() { - if (this._combinedWithAttr) { - return this._combinedWithAttr; - } - if (this.singles.every((pmf) => pmf.hasAttribution())) { - this._combinedWithAttr = this.combined; - return this._combinedWithAttr; - } - const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); - const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); - const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); - this._combinedWithAttr = normalized; - return normalized; - } - /** - * Per-label `damage value → probability mass` series for the combined, - * attribution-carrying distribution — the provenance core of the stacked - * damage-attribution chart. Convenience for - * `combinedWithAttribution().attributionByValue()`; see - * {@link PMF.attributionByValue}. - */ - attributionByValue() { - return this.combinedWithAttribution().attributionByValue(); - } - /** - * How many of the independent single PMFs can produce the given outcome - * label. Useful for "all of them succeeded" style probabilities where the - * exponent is the number of contributing attacks (see - * {@link DiceQuery.probExactlyK}). - */ - countSinglesWith(label) { - let count = 0; - for (const single of this.singles) { - if (single.hasOutcome(label)) count++; - } - return count; - } - /** - * Returns the expected damage across all possible outcomes. - * - * Example: `query.mean()` → 12.5 - * Use case: "What's my average damage per round?" - */ - mean() { - if (this._combinedProvided) { - let m = 0; - for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; - return m; - } - let totalMean = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; - } - return totalMean; - } - /** - * Returns the variance of the damage distribution. - * - * Example: `query.variance()` → 45.2 - * Use case: "How much does my damage vary from the average?" - * High variance means higher risk/reward. Lower variance means more consistent damage. - */ - variance() { - if (this._combinedProvided) { - const mu = this.mean(); - let v = 0; - for (const [damageValue, bin] of this.combined) { - const dev = damageValue - mu; - v += dev * dev * bin.p; - } - return v; - } - let totalVariance = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - if (Math.abs(mass - 1) <= this._eps) { - totalVariance += single.variance(); - } else { - let mu = 0; - for (const [d2, b] of single) mu += d2 * (b.p / mass); - let v = 0; - for (const [d2, b] of single) { - const dev = d2 - mu; - v += dev * dev * (b.p / mass); - } - totalVariance += v; - } - } - return totalVariance; - } - /** - * Returns the standard deviation of the damage distribution. - * - * Example: `query.stdev()` → 6.7 - * Use case: "What's the typical spread around my average damage?" - * Used to determine how consistent the damage is. - */ - stddev() { - return Math.sqrt(this.variance()); - } - /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ - stdev() { - return this.stddev(); - } - /** - * Returns the Cumulative Distribution Function. - */ - cdf(x) { - return this.probTotalAtMost(x); - } - /** - * Returns the probability of dealing X damage or less. - * In statistics, this is called the cumulative distribution function (CDF). - * Example: `query.cdf(20)` → 0.75 - * Use case: "What's the chance I deal 20 damage or less?" - */ - probTotalAtMost(x) { - let cumulativeProbability = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue <= x) { - cumulativeProbability += probabilityBin.p; - } - } - return cumulativeProbability; - } - /** - * Returns the Complementary Cumulative Distribution Function. - */ - ccdf(x) { - return this.probTotalAtLeast(x); - } - /** - * Returns the probability of dealing at least X damage. - * - * Example: `query.probTotalAtLeast(25)` → 0.35 - * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" - */ - probTotalAtLeast(threshold) { - let probabilitySum = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue >= threshold) { - probabilitySum += probabilityBin.p; - } - } - return probabilitySum; - } - /** - * Returns damage values at specific percentiles. - * - * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] - * Use case: "What are my 25th, 50th, and 75th percentile damage values?" - */ - percentiles(percentileValues) { - const sortedDamageValues = this.combined.support(); - if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); - const cumulativeProbabilities = []; - let runningProbabilitySum = 0; - for (const damageValue of sortedDamageValues) { - runningProbabilitySum += this.combined.map.get(damageValue).p; - cumulativeProbabilities.push(runningProbabilitySum); - } - return percentileValues.map((targetPercentile) => { - let leftBound = 0; - let rightBound = cumulativeProbabilities.length - 1; - while (leftBound <= rightBound) { - const middleIndex = Math.floor((leftBound + rightBound) / 2); - if (cumulativeProbabilities[middleIndex] >= targetPercentile) { - rightBound = middleIndex - 1; - } else { - leftBound = middleIndex + 1; - } - } - return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; - }); - } - /** - * Returns the minimum possible damage. - * - * Example: `query.min()` → 0 - * Use case: "What's the worst-case damage if everything misses?" - */ - min() { - return this.combined.min(); - } - /** - * Returns the maximum possible damage. - * - * Example: `query.max()` → 56 - * Use case: "What's the best-case damage if everything crits and rolls max?" - */ - max() { - return this.combined.max(); - } - singleProb(diceIndex, label) { - const single = this.singles[diceIndex]; - let probabilitySum = 0; - for (const [, probabilityBin] of single) { - probabilitySum += probabilityBin.count[label] || 0; - } - const mass = single.mass(); - return mass > 0 ? probabilitySum / mass : 0; - } - /** - * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it - * carries ANY of `labels`", over the n independent singles. - * - * Each single's per-event success probability is the Poisson-binomial - * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), - * computed exactly once. The binomial DP then runs once to produce the whole - * distribution, so the array-label paths of probExactlyK / probAtLeastK / - * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and - * re-running the DP per requested k. - */ - countDistribution(labels) { - const n = this.singles.length; - const successProbabilities = this.singles.map( - (single) => new _DiceQuery([single]).probabilityOf(labels) - ); - const dist = new Array(n + 1).fill(0); - dist[0] = 1; - for (const successProb of successProbabilities) { - for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { - dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; - } - dist[0] *= 1 - successProb; - } - return dist; - } - probAtLeastK(labels, k) { - const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; - const n = this.singles.length; - if (k <= 0) return 1; - if (k > n) return 0; - const dist = this.countDistribution(L); - let tail = 0; - for (let i = k; i <= n; i++) { - tail += dist[i]; - } - if (tail < 0) return 0; - if (tail > 1) return 1; - return tail; - } - /** - * Returns the probability that at least one attack has the specified outcome(s). - * - This is the complement of probAtMostK(labels, 0) - * - * Examples: - * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) - * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) - * - * Use cases: - * - "What's the chance at least one of my attacks connects?" - * - * Note: - * - * - You have to pass in an array of labels to avoid double-counting if you are - * using multiple labels. You cannot just add them. - */ - probAtLeastOne(labels) { - if (typeof labels === "string") { - labels = [labels]; - } - let productOfNonOccurrence = 1; - for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { - let combinedProbability = 0; - for (const label of labels) { - combinedProbability += this.singleProb(diceIndex, label); - } - if (combinedProbability < 0) combinedProbability = 0; - else if (combinedProbability > 1) combinedProbability = 1; - productOfNonOccurrence *= 1 - combinedProbability; - } - const result = 1 - productOfNonOccurrence; - return result < 0 ? 0 : result > 1 ? 1 : result; - } - /** - * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. - * - * Uses dynamic programming to efficiently calculate the probability distribution - * of how many attacks will have the specified outcome, accounting for different - * success probabilities across individual attacks. - * - * Example: For 3 attacks with 50% hit chance each, returns: - * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] - * - * @param label - The outcome type to count - * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) - * @returns Array where index K contains P(exactly K attacks have the label) - */ - computeBinomialProbabilities(label, maxK) { - const individualProbabilities = this.singles.map( - (_, diceIndex) => this.singleProb(diceIndex, label) - ); - const binomialProbs = new Array(maxK + 1).fill(0); - binomialProbs[0] = 1; - for (const singleProbability of individualProbabilities) { - for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { - binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; - } - binomialProbs[0] *= 1 - singleProbability; - } - return binomialProbs; - } - /** - * Returns the probability that exactly K attacks result in the specified outcome(s). - * - * Single label examples: - * - probExactlyK('hit', 2) = probability exactly 2 attacks hit - * - probExactlyK('crit', 1) = probability exactly 1 attack crits - * - probExactlyK('crit', 0) = probability no attacks crit - * - * Array examples: - * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed - * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds - * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss - * - * Use cases: - * - "What's the chance exactly one of my attacks hits?" - * - "How likely am I to get exactly 2 successes out of 3 attacks?" - * - "What's the probability that exactly half my attacks succeed?" - * - * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. - * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. - */ - probExactlyK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - return probabilityArray[k]; - } - const dist = this.countDistribution(labels); - return k >= 0 && k < dist.length ? dist[k] : 0; - } - /** - * Returns the probability that AT MOST K attacks result in the specified outcome(s). - * - * Single label examples: - * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) - * - probAtMostK('crit', 0) = probability no attacks crit - * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss - * - * Array examples: - * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds - * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) - * - * Use cases: - * - "What's the chance that at most one attack hits?" (rest miss) - * - "How likely am I to have mostly failures?" (at most 1 success) - * - "What's the probability of a really bad turn?" (at most 0 successes) - * - */ - probAtMostK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - let cumulativeSum2 = 0; - for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { - cumulativeSum2 += probabilityArray[outcomeCount]; - } - return cumulativeSum2; - } - const dist = this.countDistribution(labels); - const upper = Math.min(k, dist.length - 1); - let cumulativeSum = 0; - for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { - cumulativeSum += dist[outcomeCount]; - } - return cumulativeSum; - } - /** - * Returns the expected damage attributed to specific outcome types. - * - * Single label examples: - * - expectedDamageFrom('hit') = expected damage from hit components - * - expectedDamageFrom('crit') = expected damage from crit components - * - * Array examples: - * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success - * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses - * - * Use cases: - * - "How much damage do I expect from successful attacks?" - * - "What's the damage contribution from critical hits specifically?" - * - "How much damage comes from miss effects (like save-for-half spells)?" - */ - expectedDamageFrom(labels) { - const wanted = Array.isArray(labels) ? labels : [labels]; - let total = 0; - for (const single of this.singles) { - for (const [dmg, bin] of single) { - let p = 0; - for (const label of wanted) p += bin.count[label] ?? 0; - total += dmg * p; - } - } - return total; - } - /** - * Returns damage statistics for scenarios where AT LEAST ONE attack results in - * the specified outcome(s). - * - * This method answers "What happens when things go reasonably well?" rather than - * "What's the theoretical maximum?" It includes mixed scenarios which are more - * common and tactically relevant than pure scenarios. - * - * Single label examples: - * - damageStatsFrom('hit') = damage range when at least one attack hits - * - damageStatsFrom('crit') = damage range when at least one attack crits - * - * Array examples: - * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds - * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses - * - * Tactical Use Cases: - * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" - * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" - * - "Should I use this risky spell if it has good damage when it works?" - * - "What's my damage potential when something goes right?" (vs pure failure) - * - * Combat Planning Examples: - * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" - * (Much more useful than "You average 50 damage including complete misses") - * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" - * - Resource management: "If I hit anything, I'll likely finish this enemy" - * - * Statistical Note: - * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which - * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. - * - * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an - * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and - * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than - * E[dmg | the label occurs]. For a single attack both are the plain - * conditional figures. Use {@link probAtLeastOne} for the scenario probability. - * - * @example - * // High-level tactical planning - * const successStats = query.damageStatsFrom('hit') - * const successChance = query.probAtLeastOne('hit') - * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) - */ - damageStatsFrom(labels) { - const labelArray = typeof labels === "string" ? [labels] : labels; - let minDamage = Infinity; - let maxDamage = -Infinity; - let totalDamage = 0; - let totalCount = 0; - for (const [damage, probabilityBin] of this.combined) { - let binHasAnyLabel = false; - let binContribution = 0; - for (const label of labelArray) { - const count = probabilityBin.count[label]; - if (count && count > 0) { - binHasAnyLabel = true; - binContribution += count; - } - } - if (damage > 0 && binHasAnyLabel) { - minDamage = Math.min(minDamage, damage); - maxDamage = Math.max(maxDamage, damage); - const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; - totalDamage += damage * weightToUse; - totalCount += weightToUse; - } - } - return { - min: minDamage === Infinity ? 0 : minDamage, - max: maxDamage === -Infinity ? 0 : maxDamage, - avg: totalCount > 0 ? totalDamage / totalCount : 0, - count: totalCount - }; - } - /** - * Returns damage statistics for scenarios where ALL attacks result in the specified - * outcome, calculated by leveraging the pure partition of singles. - * - * This method answers "What's the theoretical best/worst case?" and "What are the - * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. - * - * Examples: - * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) - * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) - * - * UI and Display Use Cases: - * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) - * - "Best case scenario" vs "worst case scenario" analysis - * - Mathematical verification: "Does our hit damage calculation match manual math?" - * - Clean damage type attribution: "How much comes from base hits vs crits?" - * - * Design and Balance Use Cases: - * - Game designers: "What's the damage ceiling if someone gets lucky?" - * - Character optimization: "What's my absolute maximum potential?" - * - Ability comparison: "Which build has higher crit ceiling?" - * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" - * - * Mathematical Use Cases: - * - Validating complex calculations against simple manual math - * - Understanding damage component contributions in isolation - * - Separating luck (crit variance) from consistency (hit variance) - * - Building intuition about damage sources - * - * When to Use This vs damageStatsFrom(): - * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons - * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios - * - * Statistical Note: - * Pure scenarios (all hits, all crits) are rare but represent clear mathematical - * boundaries. These stats help understand the "shape" of your damage potential. - * - * @example - * // UI display logic - * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" - * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" - * - * // vs tactical planning (use damageStatsFrom instead) - * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios - */ - combinedDamageStats(targetLabel) { - const singleStats = this.singles.map( - (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) - ); - if (singleStats.some((stats) => stats.count === 0)) { - return { min: 0, max: 0, avg: 0, count: 0 }; - } - const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); - const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); - const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); - const combinedProb = singleStats.reduce( - (product, stats) => product * stats.count, - 1 - ); - return { - min: combinedMin, - max: combinedMax, - avg: combinedAvg, - count: combinedProb - }; - } - /** - * Returns the probability that at least one attack carries ANY of the - * specified labels (the marginal P(≥1) across the independent attacks). - * - * Examples: - * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) - * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) - * - * Use cases: - * - "What's the chance my resolution includes a success label?" - * - "How likely am I to get any hits or crits across all attacks?" - * - * Note: this must NOT be computed by summing `combined` bin probabilities. A - * single combined damage total is reachable by many outcome combinations and - * a bin can hold several labels at once, so summing `bin.p` over bins that - * contain a label over-counts. The correct marginal is the Poisson-binomial - * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. - */ - probabilityOf(labels) { - return this.probAtLeastOne(labels); - } - /** - * Returns the probability of missing (any type of miss). - * - * Example: `query.missChance()` → 0.04 - * Use case: "What's the chance I miss completely this turn?" - */ - missChance() { - return this.probabilityOf(["missDamage", "missNone"]); - } - /** - * Returns data formatted for plotting damage probability distribution. - * - * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] - * Use case: "I want to visualize my damage distribution in a chart." - */ - toChartSeries() { - return this.combined.support().map((damageValue) => ({ - x: damageValue, - y: this.combined.map.get(damageValue).p - })); - } - /** - * Returns tabular data showing damage values and their probability breakdowns. - * - * Example: `query.toLabeledTable(['hit', 'crit'])` → - * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] - * - * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." - */ - toLabeledTable(labels = []) { - return this.combined.support().map((damageValue) => { - const probabilityBin = this.combined.map.get(damageValue); - const tableRow = { - damage: damageValue, - total: probabilityBin.p - }; - for (const outcomeLabel of labels) { - tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; - } - return tableRow; - }); - } - /** - * Returns data for stacked charts with unconditional per-label probability mass per damage. - * - * - Each dataset value equals the unconditional probability mass for that label at that damage - * (i.e., `bin.count[label]`). - * - Column sums may be less than the total probability `bin.p` when you omit labels or when - * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. - * - This behavior matches tests that expect raw per-label mass (not proportional scaling). - * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. - * - * @example - * query.toStackedChartData(['hit', 'crit']) - * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} - */ - toStackedChartData(labels = [], epsilon = EPS) { - const damageValues = this.combined.support(); - const datasets = labels.map((outcomeLabel) => ({ - label: outcomeLabel, - data: damageValues.map((dmg) => { - const bin = this.combined.map.get(dmg); - const v = bin ? bin.count[outcomeLabel] || 0 : 0; - return v <= epsilon ? 0 : v; - }) - })); - return { labels: damageValues, datasets }; - } - /** - * Returns pure mathematical data for attribution charts showing outcome contributions. - * - * Automatically discovers all outcome types present in the PMF, applies filtering rules, - * and returns proportional data suitable for stacked visualization. - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and proportional data - * - * @example - * query.toAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} - */ - toAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeCount = bin.count[outcome] || 0; - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - const outcomeProbability = bin.p * outcomeFraction; - return asPercentages ? outcomeProbability * 100 : outcomeProbability; - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for damage attribution charts showing damage contribution - * from each outcome type at each damage value. - * - * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of - * bin.count (probability attribution). - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) - * @returns Pure data structure with support, outcomes, and damage attribution data - * - * @example - * query.toDamageAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} - */ - toDamageAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - if (bin.attr) { - for (const outcomeType in bin.attr) { - if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin || !bin.attr) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeDamageAttribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { - if (filterRules(outcomeName, damage)) { - totalDamageAttribution += damageAttr || 0; - } - } - if (totalDamageAttribution === 0) return 0; - const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; - return damagePercentage * bin.p * 100; - } else { - return outcomeDamageAttribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for outcome attribution charts showing which - * attack outcome combinations can produce each damage value. - * - * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks - * outcome combinations - answering "what attack outcomes produced this damage?" - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and outcome combination probabilities - * - * @example - * query.toOutcomeAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} - */ - toOutcomeAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - if (outcome === "missNone") { - const outcomeCount = bin.count[outcome] || 0; - if (outcomeCount === 0) return 0; - if (asPercentages) { - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - return outcomeFraction * bin.p * 100; - } else { - return outcomeCount; - } - } - if (!bin.attr) return 0; - const outcomeDamageContribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [, damageAttr] of Object.entries(bin.attr)) { - totalDamageAttribution += damageAttr || 0; - } - if (totalDamageAttribution === 0) return 0; - const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; - return outcomeFraction * bin.p * 100; - } else { - return outcomeDamageContribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for cumulative distribution function (CDF). - * Shows P(X ≤ x) - the probability of getting at most x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and cumulative probabilities - * - * @example - * query.toCDFSeries() - * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} - */ - toCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const cdfData = []; - for (const damage of support) { - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - cdfData.push( - asPercentages ? cumulativeProbability * 100 : cumulativeProbability - ); - } - return { - support, - data: cdfData - }; - } - /** - * Returns pure mathematical data for complementary cumulative distribution function (CCDF). - * Shows P(X ≥ x) - the probability of getting at least x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and complementary cumulative probabilities - * - * @example - * query.toCCDFSeries() - * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} - */ - toCCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const ccdfData = []; - for (const damage of support) { - const ccdf = 1 - cumulativeProbability; - ccdfData.push(asPercentages ? ccdf * 100 : ccdf); - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - } - return { - support, - data: ccdfData - }; - } - /* - Statistics snapshot of the query. - */ - /** Probability of doing strictly more than threshold damage (default >0). */ - probDamageGreaterThan(threshold = 0) { - let acc = 0; - for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; - return acc; - } - /** All outcome keys actually present (typed & ordered if you pass an order). */ - outcomeKeys(order) { - const found = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) - if (bin.count[k] && bin.count[k] > 0) found.add(k); - } - if (found.size === 0) - ["hit", "crit", "missNone"].forEach((k) => found.add(k)); - const keys = Array.from(found).filter( - (k) => order?.includes(k) ?? true - ); - if (order && order.length) - keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); - return keys; - } - /** Total probability per outcome across the PMF. */ - outcomeTotals(outcomes = this.outcomeKeys()) { - const totals = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => totals.set(o, 0)); - for (const [, row] of this.combined.map) { - for (const o of outcomes) { - const p = row.count[o] || 0; - totals.set(o, (totals.get(o) || 0) + p); - } - } - return totals; - } - /** Conditional damage range per outcome (min/avg/max of X | outcome). */ - outcomeDamageRanges(outcomes = this.outcomeKeys()) { - const table = this.toLabeledTable(outcomes); - const ranges = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); - for (const row of table) { - const dmg = row.damage; - for (const o of outcomes) { - const p = row[o] || 0; - if (p > 0) { - const r = ranges.get(o); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - } - const out = /* @__PURE__ */ new Map(); - for (const o of outcomes) { - const r = ranges.get(o); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); - } - return out; - } - /** - * Snapshot of the distribution in the exact shape the UI consumes. - * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) - * - damageRange is conditional on the outcome occurring - * - * The outcome probabilities use the correct Poisson-binomial marginals - * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), - * so they are always valid probabilities in [0,1]. - * - * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from - * the combined PMF's `count`, which the convolution accumulates as an EXPECTED - * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] - * rather than a clean conditional expectation. It is correct for a single - * attack. - */ - snapshot(order) { - const discovered = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) { - if (bin.count[k] && bin.count[k] > 0) discovered.add(k); - } - } - if (discovered.size === 0) { - for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); - } - let outcomes = Array.from(discovered); - if (order && order.length) { - const inOrder = new Set(order); - outcomes = outcomes.filter((k) => inOrder.has(k)); - const rank = new Map(order.map((k, i) => [k, i])); - outcomes.sort( - (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) - ); - } - const rows = this.toLabeledTable(outcomes); - const rangeAcc = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - rangeAcc.set(ot, { sum: 0, mass: 0 }); - } - for (const row of rows) { - const dmg = row.damage; - for (const ot of outcomes) { - const p = row[ot] || 0; - if (p <= 0) continue; - const r = rangeAcc.get(ot); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - const n = this.singles.length; - const outcomeMap = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - const r = rangeAcc.get(ot); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - outcomeMap.set(ot, { - atLeastOneProbability: this.probAtLeastOne(ot), - allProbability: this.probAtLeastK(ot, n), - damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } - }); - } - const averageDPR = this.mean(); - let damageChance = 0; - for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; - const { support, data } = this.toCDFSeries(false); - const quantile = (p) => { - if (support.length === 0) return 0; - for (let i = 0; i < support.length; i++) - if (data[i] >= p) return support[i]; - return support[support.length - 1]; - }; - const percentiles = { - p25: quantile(0.25), - p50: quantile(0.5), - p75: quantile(0.75) - }; - return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; - } - /** - * PMF Transformation Methods - * - * These methods provide a fluent API for transforming dice queries by wrapping - * the underlying PMF transformation methods. All operations work on the combined - * PMF and return new DiceQuery instances. - */ - /** - * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). - * - * @returns New DiceQuery with normalized combined PMF - */ - normalize() { - return new _DiceQuery([this.combined.normalize()]); - } - /** - * Returns a new DiceQuery with low-probability outcomes removed. - * - * @param eps Minimum probability threshold (defaults to PMF epsilon) - * @param keepFinalBin Whether to keep the highest damage bin regardless of probability - * @returns New DiceQuery with compacted combined PMF - */ - compact(eps, keepFinalBin) { - return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); - } - /** - * Returns a new DiceQuery with an additional scaled branch added. - * Useful for conditional outcomes like "30% chance of opportunity attack". - * - * @param branch DiceQuery to add as a scaled branch - * @param probability Probability of the branch occurring (0-1) - * @returns New DiceQuery combining this query with the scaled branch - * - * @example - * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); - * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); - */ - addScaled(branch, probability) { - return new _DiceQuery([ - this.combined.addScaled(branch.combined, probability) - ]); - } - /** - * Returns a new DiceQuery with all probabilities scaled by a factor. - * Used for conditional scenarios where the entire outcome has reduced probability. - * - * @param factor Scaling factor for probabilities - * @returns New DiceQuery with scaled probabilities - * - * @example - * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario - */ - scaleMass(factor) { - return new _DiceQuery([this.combined.scaleMass(factor)]); - } - totalMass() { - return this.combined.mass(); - } - /** - * Returns a new DiceQuery with damage values transformed by a function. - * Useful for applying modifiers, resistances, or other damage transformations. - * - * @param damageTransformFunction Function to transform each damage value - * @returns New DiceQuery with transformed damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage - * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage - */ - mapDamage(damageTransformFunction) { - return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); - } - /** - * Returns a new DiceQuery with damage values scaled by a factor. - * Convenient wrapper around mapDamage for multiplicative scaling. - * - * @param factor Scaling factor for damage values - * @param rounding Rounding method: "floor" (default), "round", or "ceil" - * @returns New DiceQuery with scaled damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const doubled = baseAttack.scaleDamage(2); // Double damage - * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded - */ - scaleDamage(factor, rounding = "floor") { - return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); - } - /** - * Returns a new DiceQuery combining this query with another via convolution. - * Equivalent to rolling both queries independently and adding results. - * It is important to use this rather than combing()ing the PMFs directly! - * This method maintains the provenance of the PMFs which is needed for damage attribution. - * Combining the .combined PMFs directly is still valid for DPR calculations but - * is not statistically sound for queries. - * - * @param other DiceQuery to combine with - * @param eps Optional epsilon for precision control - * @returns New DiceQuery representing the combined outcome - * - * @example - * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); - * const bothAttacks = mainAttack.convolve(bonusAttack); - */ - convolve(other) { - const singles = [...this.singles, ...other.singles]; - return new _DiceQuery(singles); - } - /** - * First-success split over an ordered list of DISTINCT single-swing PMFs. - * Each PMF may have different success/subset probabilities (from labels). - * - * successOutcome: e.g., ["success"] or ["hit", "crit"] - * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success - * - * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] - */ - firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { - const pmfs = this.singles; - if (!pmfs.length) { - throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); - } - const toArr = (x) => Array.isArray(x) ? x : [x]; - const clamp01 = (x) => Math.max(0, Math.min(1, x)); - const tol = Math.max(eps, 8 * Number.EPSILON); - const per = pmfs.map((pmf) => { - const dq = new _DiceQuery([pmf]); - const pS = dq.probAtLeastOne(toArr(successOutcome)); - const pB = dq.probAtLeastOne(toArr(subsetOutcome)); - if (pB - pS > eps) { - throw new Error( - "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." - ); - } - return { pS, pB }; - }); - let missSoFar = 1; - let pFirstSubset = 0; - let pFirstNonSubset = 0; - let pNone = 1; - for (const { pS, pB } of per) { - pFirstSubset += missSoFar * pB; - pFirstNonSubset += missSoFar * (pS - pB); - const miss = 1 - pS; - missSoFar *= miss; - pNone *= miss; - } - const pAny = 1 - pNone; - const a = clamp01(pFirstNonSubset); - const b = clamp01(pFirstSubset); - const any = clamp01(pAny); - const none = clamp01(pNone); - if (Math.abs(a + b - any) > tol * Math.max(1, any)) { - throw new Error( - `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` - ); - } - return [a, b, any, none]; - } -}; -_DiceQuery.DEFAULT_OUTCOMES = [ - "hit", - "crit", - "missNone" -]; -var DiceQuery = _DiceQuery; -var pmfCache = new LRUCache(1e3); -var _PMF = class _PMF { - constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { - this.map = map; - this.epsilon = epsilon; - this.normalized = normalized; - this.identifier = identifier; - this._preservedProvenance = _preservedProvenance; - } - static empty(epsilon = EPS, identifier = "empty") { - return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); - } - // This has a single bin at value 0, mass of 1 - static zero(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "zero"); - } - static delta(value, epsilon = EPS) { - return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); - } - /** - * Point mass at damage 0 tagged with the canonical `missNone` outcome. - * - * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the - * builder's attack-resolution vocabulary. This uses the `missNone` - * {@link OutcomeType} that the attribution charts and outcome stats key on, - * so it is the correct "clean miss / no damage" delta for provenance-aware - * mixtures feeding those consumers. - */ - static missNone(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "missNone"); - } - // This creates a single bin at value 0, but with weight 0. - static emptyMass() { - return _PMF.zero().scaleMass(0); - } - // Makes PMF iterable over [damage, bin] pairs. - [Symbol.iterator]() { - return this.map[Symbol.iterator](); - } - static clearCache() { - pmfCache.clear(); - } - /** - * Creates a conditional PMF from two branches (success and failure) and a probability. - * This is the core logic for modeling any probabilistic event where there are two - * distinct outcomes. - */ - static branch(successPMF, failurePMF, successProbability) { - let p = successProbability; - if (!Number.isFinite(p)) p = 0; - if (p < 0) p = 0; - if (p > 1) p = 1; - const q = 1 - p; - if (p === 0) return failurePMF.scaleMass(1); - if (p === 1) return successPMF.scaleMass(1); - const eps = successPMF.epsilon ?? failurePMF.epsilon; - const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; - const resultMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of failurePMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); - } - for (const [damageValue, bin] of successPMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); - } - return new _PMF(resultMap, eps, false, id); - } - /** - * withProbability() - * - * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). - * - * Think of this as a shortcut for: - * pmf.gate(p, PMF.zero()) - * - * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, - * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. - * - * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. - * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. - * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) - * use query.firstSuccessSplit() to get the exact probabilities. - * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). - * - */ - static withProbability(successPMF, probability) { - return _PMF.branch(successPMF, _PMF.zero(), probability); - } - /** - * gate() - * - * A conditional wrapper around branch() that applies this PMF with probability `p`, - * and applies a provided fallback PMF otherwise. - * - * This is useful for modeling a binary choice between two outcomes: - * - The "success" outcome (this PMF) happens with probability `p`. - * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. - * - * Examples: - * - 25% chance to include an opportunity attack, otherwise nothing: - * attackPMF.gate(0.25, PMF.zero()) - * - * - 50% chance to deal fireball damage, otherwise cone of cold damage: - * fireballPMF.gate(0.5, coneOfColdPMF) - * - * Relationship to other helpers: - * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. - * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). - * - * @param p Probability of applying this PMF (between 0 and 1). - * @param fallback PMF to apply when this PMF is *not* selected. - * @returns A new PMF representing the weighted mixture of this PMF and the fallback. - */ - gate(p, fallback) { - return _PMF.branch(this, fallback, p); - } - /** - * PMF.exclusive() - * - * Builds a single PMF from a set of mutually exclusive weighted outcomes. - * Exactly one of the provided options will occur. - * - * Each option has: - * - A PMF representing its outcome (e.g., damage dice). - * - A weight representing its probability of being selected. - * - * Notes: - * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for floating point rounding. - */ - static exclusive(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight) || weight < -eps) { - throw new Error(`PMF.exclusive: invalid weight ${weight}.`); - } - } - let totalWeight = items.reduce((s, { weight }) => s + weight, 0); - if (Math.abs(totalWeight) <= eps) totalWeight = 0; - if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; - if (totalWeight > 1 + EPS) { - throw new Error( - `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` - ); - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (weight > eps) out = out.addScaled(pmf, weight); - } - const leftover = Math.max(0, 1 - totalWeight); - if (leftover > eps) { - out = out.addScaled(_PMF.zero(), leftover); - } - return out; - } - /** - * PMF.mix() - * - * Builds a PMF as a linear combination of input PMFs with the given weights. - * Unlike `exclusive`, this does NOT: - * - enforce that weights sum to 1 - * - add leftover probability to δ0 (PMF.zero()) - * - * Use when outcomes are not mutually exclusive, or for interpolation/blending. - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for skipping tiny weights. - */ - static mix(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight)) { - throw new Error(`PMF.mix: invalid weight ${weight}.`); - } - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (Math.abs(weight) <= eps) continue; - out = out.addScaled(pmf, weight); - } - return out; - } - /** - * Adds damage attribution metadata to this PMF based on existing count metadata. - * For each bin, sets attr[outcome] = damage × count[outcome]. - * - * This enables damage attribution charts to work with builder-generated PMFs. - * The parser generates attr automatically, but builder PMFs only have count. - * - * @returns New PMF with attr field populated in each bin - */ - /** - * Returns true if this PMF already carries damage attribution metadata. - * - * Only the first positive-damage bin is inspected (parser-generated PMFs - * populate `attr` uniformly), so this is O(1) in practice. - */ - hasAttribution() { - for (const [damage, bin] of this.map) { - if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { - return true; - } - if (damage > 0) break; - } - return false; - } - withAttribution() { - if (this.hasAttribution()) return this; - const newMap = /* @__PURE__ */ new Map(); - for (const [damage, bin] of this.map) { - const attr = {}; - for (const outcome in bin.count) { - const probability = bin.count[outcome]; - if (probability > 0) { - attr[outcome] = damage * probability; - } - } - newMap.set(damage, { - p: bin.p, - count: { ...bin.count }, - attr: Object.keys(attr).length > 0 ? attr : void 0 - }); - } - return new _PMF( - newMap, - this.epsilon, - this.normalized, - `${this.identifier}~attr` - ); - } - /** - * General-purpose N-way mixture. - * weights: Array of [weight, PMF]. - * - * Example: PMF.mixN([ - * [pMiss, zero], - * [pHit, hitPMF], - * [pCrit, critPMF], - * ]); - */ - static mixN(weights, eps = EPS) { - const filtered = weights.filter(([w]) => w > eps); - if (filtered.length === 0) { - return _PMF.emptyMass(); - } - let acc = null; - let sum = 0; - for (const [w, pmf] of filtered) { - if (acc === null) { - acc = pmf; - sum = w; - } else { - const q = w / (sum + w); - acc = _PMF.branch(pmf, acc, q); - sum += w; - } - } - return acc ?? _PMF.emptyMass(); - } - // This is a convenience method for when we use power - // TODO: It can be smarter in the future, and we can also add it to query - // That way statistics operations on invalid PMFs can throw an error - // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? - setPreservedProvenance(preserved) { - if (!this._preservedProvenance && preserved) { - throw new Error( - "Preserved provenance is already set to false, cannot fix that" - ); - } - this._preservedProvenance = preserved; - } - preservedProvenance() { - return this._preservedProvenance; - } - getPowerCacheKey(n, eps) { - const id = this.identifier; - let key = `${id}`; - for (let i = 1; i < n; i++) key += `+${id}`; - return `${key}@${eps}`; - } - /** - * Efficiently computes this PMF convolved with itself `n` times. - * Uses exponentiation by squaring to reduce total convolutions. - * n must be a positive integer. - * * - * * NOTE: This folds multiple independent attacks into a single PMF. - * As a result, The power() method causes a loss of data provenance. - * This is ONLY SAFE if you are trying to calculate masses. - * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). - */ - power(n, eps = this.epsilon) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("power(n): n must be a positive integer"); - } - if (n === 1) return this; - const epsilon = eps ?? this.epsilon; - const key = this.getPowerCacheKey(n, epsilon); - { - const cached = pmfCache?.get(key); - if (cached) return cached; - } - let base = this.normalized ? this : this.normalize(); - let result = base; - let exp = n - 1; - while (exp > 0) { - if (exp & 1) { - result = result.convolve(base, epsilon); - } - exp >>= 1; - if (exp > 0) { - base = base.convolve(base, epsilon); - } - } - result.setPreservedProvenance(false); - { - pmfCache?.set(key, result); - } - return result; - } - /* - * Helper for chaining multiple identical attacks - */ - replicate(n) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("replicate(n): n must be a positive integer"); - } - if (n === 1) return [this]; - return Array.from({ length: n }, () => this); - } - mass() { - if (this._totalMass === void 0) { - let totalProbabilityMass = 0; - for (const { p } of this.map.values()) { - totalProbabilityMass += p; - } - this._totalMass = totalProbabilityMass; - } - return this._totalMass; - } - outcomeMass(outcome) { - let totalProbabilityMass = 0; - for (const { p, count } of this.map.values()) { - totalProbabilityMass += p * (count[outcome] ?? 0); - } - return totalProbabilityMass; - } - // Helper for testing - faceTotal() { - return [...this.map.keys()].reduce((sum, key) => sum + key, 0); - } - normalize() { - if (this.normalized) return this; - const normalizationFactor = this.mass(); - if (normalizationFactor === 0) return this; - const normalizedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const normalizedCount = {}; - for (const labelKey in probabilityBin.count) { - normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; - } - let normalizedAttributes; - if (probabilityBin.attr) { - normalizedAttributes = {}; - for (const labelKey in probabilityBin.attr) { - normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; - } - } - normalizedMap.set(damageValue, { - p: probabilityBin.p / normalizationFactor, - count: normalizedCount, - attr: normalizedAttributes - }); - } - return new _PMF(normalizedMap, this.epsilon, true, this.identifier); - } - /** - * Returns a copy with negligible probabilities removed (p < eps). - * If keepFinalBin is true, the bin with the largest key is always kept, - * even if its probability is below eps. count/attr submaps are still cleaned. - */ - compact(eps = this.epsilon, keepFinalBin = false) { - let maxKey = -Infinity; - if (keepFinalBin) { - for (const key of this.map.keys()) { - if (key > maxKey) maxKey = key; - } - } - const compactedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; - if (!shouldKeep) continue; - const cleanedBin = _PMF.cloneBin(probabilityBin); - for (const labelKey in cleanedBin.count) { - if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { - delete cleanedBin.count[labelKey]; - } - } - if (cleanedBin.attr) { - for (const labelKey in cleanedBin.attr) { - if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { - delete cleanedBin.attr[labelKey]; - } - } - if (Object.keys(cleanedBin.attr).length === 0) { - cleanedBin.attr = void 0; - } - } - compactedMap.set(damageValue, cleanedBin); - } - return new _PMF(compactedMap, eps, this.normalized, this.identifier); - } - // Note: The "support" of a PMF is the set of all non-zero probability outcomes. - // This returns all damage values with non-zero probability, sorted ascending. - support() { - if (this._support === void 0) { - this._support = [...this.map.keys()].sort((a, b) => a - b); - } - return this._support; - } - // Minimum possible damage value. - min() { - if (this._min === void 0) { - const support = this.support(); - this._min = support.length > 0 ? support[0] : 0; - } - return this._min; - } - // Maximum possible damage value. - max() { - if (this._max === void 0) { - const support = this.support(); - this._max = support.length > 0 ? support[support.length - 1] : 0; - } - return this._max; - } - /** - * Returns the expected (mean) damage value. - * Cached for performance since this requires iterating through all bins. - */ - mean() { - if (this._mean === void 0) { - let totalSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - totalSum += damageValue * probabilityBin.p; - } - this._mean = totalSum; - } - return this._mean; - } - /** - * Returns the variance of the damage distribution. - * Cached for performance since this requires mean calculation plus iteration. - */ - variance() { - if (this._variance === void 0) { - const meanValue = this.mean(); - let varianceSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - const deviationFromMean = damageValue - meanValue; - varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; - } - this._variance = varianceSum; - } - return this._variance; - } - /** - * Returns the standard deviation of the damage distribution. - */ - stdev() { - if (this._stdev === void 0) { - this._stdev = Math.sqrt(this.variance()); - } - return this._stdev; - } - /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ - static cloneBin(bin) { - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ - static scaleBin(bin, factor) { - const count = {}; - for (const k in bin.count) { - count[k] = bin.count[k] * factor; - } - let attr; - if (bin.attr) { - attr = {}; - for (const k in bin.attr) { - attr[k] = bin.attr[k] * factor; - } - } - return { p: bin.p * factor, count, attr }; - } - static mergeInto(destinationMap, damageValue, binToAdd) { - const existingBin = destinationMap.get(damageValue); - if (!existingBin) { - destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); - return; - } - existingBin.p += binToAdd.p; - for (const labelKey in binToAdd.count) { - existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; - } - if (binToAdd.attr) { - if (!existingBin.attr) { - existingBin.attr = {}; - } - for (const labelKey in binToAdd.attr) { - existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; - } - } - } - // Convenience method - add(other) { - return this.addScaled(other, 1); - } - /** - * Returns a new PMF with a scaled branch added to this one. - * The branch PMF is scaled by the given probability before merging - * This will be very useful for conditional effects and for being - * able to model "I can probably have this opportunity attack 40% of rounds" - * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes - */ - addScaled(branch, probability) { - if (probability === 0) return this; - const resultMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of this.map) { - resultMap.set(dmg, _PMF.cloneBin(bin)); - } - for (const [damageValue, probabilityBin] of branch.map) { - _PMF.mergeInto( - resultMap, - damageValue, - _PMF.scaleBin(probabilityBin, probability) - ); - } - return new _PMF( - resultMap, - this.epsilon, - false, - `${this.identifier}+scaled(${branch.identifier},${probability})` - ); - } - /** - * Redistributes probability mass to model an effect that only occurs with - * probability `frequency` — a conditional attack, an on-hit rider, or a - * sub-one AoE target fraction. - * - * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, - * per-label `count`, AND per-label `attr` — and the freed mass is moved into - * the miss bin at damage 0, tagged with the canonical `missNone` outcome. - * Total probability mass is preserved. - * - * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage - * attribution (`attr`) intact, so a frequency-scaled PMF still renders - * correctly in the damage-attribution charts. - * - * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` - * collapses all mass into the miss bin. The miss outcome is assumed to be - * encoded at damage value 0. - * - * @param frequency Probability in [0, 1] that the effect occurs. - */ - applyHitFrequency(frequency) { - if (!Number.isFinite(frequency) || frequency >= 1) return this; - const freq = Math.max(0, frequency); - const pMiss = this.pAt(0); - const pHit = 1 - pMiss; - const newMissMass = pMiss + (1 - freq) * pHit; - const newMap = /* @__PURE__ */ new Map(); - newMap.set(0, { - p: newMissMass, - count: { [MISS_NONE_OUTCOME]: newMissMass }, - attr: {} - }); - for (const [damage, bin] of this.map) { - if (damage <= 0) continue; - newMap.set(damage, _PMF.scaleBin(bin, freq)); - } - return new _PMF( - newMap, - this.epsilon, - false, - `freq(${this.identifier},${freq})` - ); - } - scaleMass(factor) { - if (factor === 1) return this; - const scaledMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); - } - return new _PMF( - scaledMap, - this.epsilon, - false, - `scale(${this.identifier},${factor})` - ); - } - mapDamage(damageTransformFunction) { - const transformedMap = /* @__PURE__ */ new Map(); - for (const [originalDamage, probabilityBin] of this.map) { - const transformedDamage = damageTransformFunction(originalDamage); - _PMF.mergeInto( - transformedMap, - transformedDamage, - _PMF.cloneBin(probabilityBin) - ); - } - return new _PMF( - transformedMap, - this.epsilon, - this.normalized, - `map(${this.identifier})` - ); - } - scaleDamage(factor, rounding = "floor") { - const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; - return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); - } - getPMFCombineCacheKey(p1, p2, eps, raw) { - const [id1, id2] = [p1.identifier, p2.identifier].sort(); - return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; - } - /** - * A small content fingerprint (mass + bin count + face sum) so convolution - * cache keys change if the underlying numbers do. Memoized because a PMF is - * immutable once constructed — this avoids re-summing every key on each - * convolve() call (including cache hits). - */ - fingerprint() { - if (this._fingerprint === void 0) { - let faceSum = 0; - for (const k of this.map.keys()) faceSum += k; - this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; - } - return this._fingerprint; - } - convolve(other, eps, raw = false) { - const epsilon = eps ?? this.epsilon; - const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); - const A0 = norm(this); - const B0 = norm(other); - const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; - const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); - const cached = pmfCache?.get(cacheKey); - if (cached) return cached; - const combinedMap = /* @__PURE__ */ new Map(); - for (const [aVal, aBin] of A.map) { - const ap = aBin.p; - const aCount = aBin.count; - const aAttr = aBin.attr; - for (const [bVal, bBin] of B.map) { - const bp = bBin.p; - const dmg = aVal + bVal; - let dest = combinedMap.get(dmg); - if (dest === void 0) { - dest = { p: 0, count: {} }; - combinedMap.set(dmg, dest); - } - dest.p += ap * bp; - const dc = dest.count; - for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; - for (const k in bBin.count) - dc[k] = (dc[k] || 0) + bBin.count[k] * ap; - if (aAttr || bBin.attr) { - let da = dest.attr; - if (da === void 0) { - da = {}; - dest.attr = da; - } - if (aAttr) - for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; - if (bBin.attr) - for (const k in bBin.attr) - da[k] = (da[k] || 0) + bBin.attr[k] * ap; - } - } - } - let result = new _PMF( - combinedMap, - epsilon, - !raw, - `${A.identifier}${raw ? "*" : "+"}${B.identifier}` - ); - const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); - const mGot = result.mass(); - if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { - result = result.scaleMass(mExp / mGot); - } - if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) - result = result.normalize(); - pmfCache?.set(cacheKey, result); - return result; - } - // 3) Nice wrapper so you can call pmf.combineRaw(other) - combineRaw(other, eps) { - return this.convolve(other, eps, true); - } - // Reduce a list of PMFs by left-folding convolve() with the given eps - static reduceConvolveLeft(pmfList, eps) { - let result = pmfList[0]; - for (let i = 1; i < pmfList.length; i++) { - result = result.convolve(pmfList[i], eps); - } - return result; - } - /** - * Convolves multiple PMFs using linear convolution with automatic caching. - * Uses a left-to-right accumulation approach for maximum cache reuse. - * Each convolve() call automatically uses the convolution cache for performance. - * - * This linear approach provides better cache hits than pairwise because: - * - Intermediate results are more predictable and stable - * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) - * - Order-independent cache keys work better with consistent build patterns - */ - static convolveMany(pmfList, eps = EPS) { - if (pmfList.length === 0) return _PMF.empty(eps); - if (pmfList.length === 1) return pmfList[0]; - return _PMF.reduceConvolveLeft(pmfList, eps); - } - /** - * Returns a plain, JSON-serializable representation of this PMF. - * - * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces - * the expected output (no double-encoding). Use {@link PMF.fromJSON} to - * reconstruct, or {@link PMF.toJSONString} if you need the string directly. - */ - toJSON() { - return { - bins: [...this.map.entries()], - normalized: this.normalized, - identifier: this.identifier - }; - } - /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ - toJSONString() { - return JSON.stringify(this); - } - static fromJSON(jsonData) { - return new _PMF( - new Map(jsonData.bins), - EPS, - !!jsonData.normalized, - jsonData.identifier || "fromJSON" - ); - } - /** - * Relative pruning with optional top-K floor. - * Keeps bins with p >= epsRel * peak, always keeps min and max damage, - * optionally guarantees at least `minBins` survivors by adding top-K. - * Returns a new, non-normalized PMF. - */ - prune(epsRel, minBins = 0) { - const size = this.map.size; - if (size === 0) return this; - let peak = 0; - let minDamage = Number.POSITIVE_INFINITY; - let maxDamage = Number.NEGATIVE_INFINITY; - for (const [dmg, bin] of this.map) { - if (bin.p > peak) peak = bin.p; - if (dmg < minDamage) minDamage = dmg; - if (dmg > maxDamage) maxDamage = dmg; - } - if (peak === 0) - return new _PMF(new Map(this.map), epsRel, false, this.identifier); - const thresh = epsRel * peak; - const entries = [...this.map.entries()]; - const survivorsByDmg = /* @__PURE__ */ new Map(); - const protect = (d2) => { - const b = this.map.get(d2); - if (b) survivorsByDmg.set(d2, b); - }; - protect(minDamage); - if (maxDamage !== minDamage) protect(maxDamage); - for (const [dmg, bin] of entries) { - if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); - } - if (minBins > 0 && survivorsByDmg.size < minBins) { - entries.sort((a, b) => b[1].p - a[1].p); - for (const [dmg, bin] of entries) { - if (!survivorsByDmg.has(dmg)) { - survivorsByDmg.set(dmg, bin); - if (survivorsByDmg.size >= minBins) break; - } - } - } - const prunedMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of survivorsByDmg) { - const newCount = {}; - for (const k in bin.count) { - const v = bin.count[k]; - if (Math.abs(v) >= thresh) newCount[k] = v; - } - let newAttr; - if (bin.attr) { - for (const k in bin.attr) { - const v = bin.attr[k]; - if (Math.abs(v) >= thresh) { - if (!newAttr) newAttr = {}; - newAttr[k] = v; - } - } - } - prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); - } - return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); - } - /** Probability mass at exactly x. */ - pAt(x) { - return this.map.get(x)?.p ?? 0; - } - /** - * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. - * Assumes a miss is encoded as the damage-0 bin (the convention used across - * attack/save PMFs). The dual of {@link missProbability}. - */ - hitProbability() { - return 1 - this.pAt(0); - } - /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ - missProbability() { - return this.pAt(0); - } - /** - * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width - * damage buckets, aggregating probability mass (and `count`/`attr` - * provenance) into each bucket's start value. Returns this PMF unchanged when - * its integer support already fits within `maxBuckets`. - * - * This is a lossy display/downsampling transform (bucket start replaces the - * exact damage value) — use it for charting wide distributions, not for DPR - * math. - */ - rebin(maxBuckets) { - if (!(maxBuckets > 0)) return this; - const support = this.support(); - if (support.length === 0) return this; - const min = support[0]; - const max = support[support.length - 1]; - const range = max - min; - if (range + 1 <= maxBuckets) return this; - const binSize = Math.ceil((range + 1) / maxBuckets); - return this.mapDamage((d2) => min + Math.floor((d2 - min) / binSize) * binSize); - } - /** Dense integer support from min..max (inclusive). - * Useful for showing empty bars in charts. - */ - denseSupport() { - const s = this.support(); - if (s.length === 0) return []; - const lo = Math.min(...s), hi = Math.max(...s); - return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( - (a, b) => a - b - ); - } - /** CDF at x: P(X ≤ x). */ - cdfAt(x) { - let acc = 0; - for (const [val, bin] of this.map) if (val <= x) acc += bin.p; - return acc; - } - /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ - quantile(p) { - if (this.map.size === 0) return 0; - const s = this.support().sort((a, b) => a - b); - let acc = 0; - for (const x of s) { - acc += this.pAt(x); - if (acc >= p) return x; - } - return s[s.length - 1]; - } - /** Get outcome probability at specific damage value. */ - outcomeAt(damage, outcome) { - return this.map.get(damage)?.count[outcome] ?? 0; - } - /** Get all outcome types present in this PMF. */ - outcomes() { - const outcomeSet = /* @__PURE__ */ new Set(); - for (const [, bin] of this.map) { - for (const outcome in bin.count) { - if (bin.count[outcome] > 0) { - outcomeSet.add(outcome); - } - } - } - return Array.from(outcomeSet).sort(); - } - /** Get total probability of an outcome across all damage values. */ - outcomeProbability(outcome) { - let total = 0; - for (const [, bin] of this.map) { - total += bin.count[outcome] ?? 0; - } - return total; - } - /** Get damage attribution for an outcome at specific damage value. */ - outcomeAttributionAt(damage, outcome) { - return this.map.get(damage)?.attr?.[outcome] ?? 0; - } - /** Get all outcome data at specific damage value. */ - binAt(damage) { - const bin = this.map.get(damage); - if (!bin) return null; - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Check if outcome exists in this PMF. */ - hasOutcome(outcome) { - for (const [, bin] of this.map) { - if ((bin.count[outcome] ?? 0) > 0) { - return true; - } - } - return false; - } - /** - * Split each damage value's probability mass across outcome labels, returning - * per-label maps of `damage value → probability mass attributable to that - * label`. Summing over labels at a given value recovers that value's `p`. - * - * Damage-bearing bins are split by `attr` weight (the share of damage each - * outcome contributed); the clean-miss bin at 0 is split by `count` weight - * (there is no damage to attribute). Attribution is computed on demand via - * {@link withAttribution} when absent, so builder-generated PMFs work too. - * - * This is the provenance core of the stacked damage-attribution chart — the - * caller only maps these series into its rendering format (colors, binning, - * axis labels). - */ - attributionByValue() { - const src = this.hasAttribution() ? this : this.withAttribution(); - const result = /* @__PURE__ */ new Map(); - const add = (label, damage, mass) => { - if (!(mass > 0)) return; - let series = result.get(label); - if (!series) { - series = /* @__PURE__ */ new Map(); - result.set(label, series); - } - series.set(damage, (series.get(damage) ?? 0) + mass); - }; - for (const [damage, bin] of src.map) { - const p = bin.p || 0; - if (p <= 0) continue; - const isMissBin = damage === 0; - if (isMissBin) { - let totalCount = 0; - for (const k in bin.count) totalCount += bin.count[k] || 0; - if (totalCount > 0) { - const c = bin.count[MISS_NONE_OUTCOME] || 0; - add(MISS_NONE_OUTCOME, damage, c / totalCount * p); - } - continue; - } - let totalAttr = 0; - if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; - if (bin.attr && totalAttr > 0) { - for (const k in bin.attr) { - if (k === MISS_NONE_OUTCOME) continue; - add(k, damage, (bin.attr[k] || 0) / totalAttr * p); - } - } - } - return result; - } - tailProbGE(t) { - let s = 0; - for (const [x, bin] of this) { - if (bin.p > 0 && x >= t) s += bin.p; - } - return s; - } - tailProbGT(t) { - let s = 0; - for (const [x, rec] of this) { - if (x > t) s += rec.p; - } - return s; - } - /** - * Returns a new PMF containing only bins where the specified outcome has non-zero probability. - * This creates a marginal distribution for the given outcome type, with probabilities - * scaled to represent the unconditional mass attributable to that outcome. - */ - filterOutcome(outcome) { - const filteredMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of this.map) { - const outcomeCount = bin.count[outcome] ?? 0; - const totalCount = Object.values(bin.count ?? {}).reduce( - (a, b) => (a ?? 0) + (b ?? 0), - 0 - ); - if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { - const proportion = outcomeCount / totalCount; - const newP = bin.p * proportion; - const newCount = { [outcome]: outcomeCount }; - let newAttr; - if (bin.attr && bin.attr[outcome] !== void 0) { - newAttr = { [outcome]: bin.attr[outcome] * proportion }; - } - filteredMap.set(damageValue, { - p: newP, - count: newCount, - attr: newAttr - }); - } - } - return new _PMF( - filteredMap, - this.epsilon, - false, - // don't normalize by default - `filter(${this.identifier},${outcome})` - ); - } - /** - * Calculates probabilities for first-success outcomes across n independent attempts. - * - * @param pSuccess - Total probability of any success on a single attempt. - * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). - * @param n - Number of independent attempts. - * - * Returns: - * - pSpecificSuccess: Probability that the first success was of the "special" type - * - pGeneralSuccess: Probability that the first success was of the non-special type - * - pNone: Probability that no successes occurred - * - pAny: Probability that at least one success occurred - */ - static firstSuccessWeights(pSuccess, pSpecial, n) { - if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { - throw new Error( - `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` - ); - } - const pFail = 1 - pSuccess; - const pFailAll = Math.pow(pFail, n); - const pAny = 1 - pFailAll; - const denom = pSuccess === 0 ? 1 : pSuccess; - const pSpecificSuccess = pSpecial * pAny / denom; - const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; - const pNone = 1 - pSpecificSuccess - pGeneralSuccess; - return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; - } - mapValues(f, eps = EPS, opts) { - const rounding = opts?.rounding ?? "none"; - const preserveCounts = opts?.preserveCounts ?? true; - const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; - const probs = /* @__PURE__ */ new Map(); - const counts = /* @__PURE__ */ new Map(); - for (const [v, bin] of this) { - if (Math.abs(bin.p) < eps) continue; - const u = round(f(v)); - probs.set(u, (probs.get(u) ?? 0) + bin.p); - if (preserveCounts) { - const src = bin.count; - if (src) { - const dest = counts.get(u) ?? {}; - for (const k in src) { - dest[k] = (dest[k] ?? 0) + src[k]; - } - counts.set(u, dest); - } - } - } - const internal = /* @__PURE__ */ new Map(); - for (const [u, p] of probs) { - internal.set(u, { p, count: counts.get(u) ?? {} }); - } - return _PMF.fromMap( - new Map(Array.from(internal, ([u, b]) => [u, b.p])), - eps - ); - } - static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { - const filtered = []; - for (const [v, p] of m) { - if (!Number.isFinite(v) || !Number.isFinite(p)) continue; - if (p <= 0 || Math.abs(p) < eps) continue; - if (requireIntegerValues && !Number.isInteger(v)) { - throw new Error(`fromMap: non-integer outcome ${v}`); - } - filtered.push([v, p]); - } - if (filtered.length === 0) { - throw new Error("fromMap: empty or invalid input map"); - } - let sum = 0; - let c = 0; - for (const [, p] of filtered) { - const y = p - c; - const t = sum + y; - c = t - sum - y; - sum = t; - } - if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); - filtered.sort((a, b) => a[0] - b[0]); - const internal = /* @__PURE__ */ new Map(); - for (const [v, p] of filtered) { - internal.set(v, { p: p / sum, count: {} }); - } - return new _PMF(internal, eps); - } - query() { - return new DiceQuery(this); - } -}; -// Unique ID generator for anonymous PMFs to avoid cache key collisions -_PMF.__anonIdCounter = 1; -var PMF = _PMF; - -// src/pmf/mixture.ts -var Mixture = class _Mixture { - constructor(eps = EPS) { - this.totals = /* @__PURE__ */ new Map(); - // raw mass per outcome (pre-normalization) - this.labelMass = /* @__PURE__ */ new Map(); - this.eps = Number.isFinite(eps) ? eps : EPS; - } - /** Remove all accumulated state. */ - clear() { - this.totals.clear(); - this.labelMass.clear(); - return this; - } - /** Number of distinct outcome values currently accumulated. */ - size() { - return this.totals.size; - } - /** Whether a label was ever added. */ - hasLabel(label) { - for (const bag of this.labelMass.values()) if (bag[label]) return true; - return false; - } - /** - * Add a labeled component with a mixture weight. - * Weight can be any positive finite number. Very small contributions are pruned by eps. - */ - add(label, pmf, weight = 1) { - if (!Number.isFinite(weight) || weight <= 0) return this; - for (const [v, bin] of pmf) { - const p = bin.p; - if (p <= 0) continue; - const add = weight * p; - if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; - this.totals.set(v, (this.totals.get(v) ?? 0) + add); - const bag = this.labelMass.get(v) ?? {}; - bag[label] = (bag[label] ?? 0) + add; - this.labelMass.set(v, bag); - } - return this; - } - buildPMF(eps = EPS) { - let grand = 0; - let c = 0; - for (const m of this.totals.values()) { - const y = m - c; - const t = grand + y; - c = t - grand - y; - grand = t; - } - if (!(grand > 0)) throw new Error("Mixture: zero total mass"); - const internal = /* @__PURE__ */ new Map(); - for (const [v, m] of this.totals) { - if (m <= 0 || Math.abs(m) < this.eps) continue; - const count = this.labelMass.get(v) ?? {}; - internal.set(v, { p: m / grand, count }); - } - return new PMF(internal, eps); - } - /** - * Produce normalized *per-label* PMFs (labels independent). - * These are unlabeled PMFs built from the raw mass of that label alone. - */ - byOutcome() { - const labels = /* @__PURE__ */ new Set(); - for (const bag of this.labelMass.values()) { - for (const k of Object.keys(bag)) labels.add(k); - } - const out = {}; - for (const label of labels) { - const m = /* @__PURE__ */ new Map(); - for (const [v, bag] of this.labelMass) { - const w = bag[label]; - if (w && Math.abs(w) >= this.eps) m.set(v, w); - } - if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); - } - return out; - } - /** - * Mixture weights per label, normalized to sum to 1 over labels that appeared. - * Uses raw mass before per-outcome normalization. - */ - weights() { - const res = {}; - for (const [, bag] of this.labelMass) { - for (const [lab, w] of Object.entries(bag)) { - if (!Number.isFinite(w) || w <= 0) continue; - res[lab] = (res[lab] ?? 0) + w; - } - } - let total = 0; - let c = 0; - for (const v of Object.values(res)) { - const y = v - c; - const t = total + y; - c = t - total - y; - total = t; - } - if (total > 0) { - for (const k in res) res[k] = res[k] / total; - } - return res; - } - toJSON() { - return { - totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), - labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), - eps: this.eps - }; - } - static mix(items, eps = EPS) { - const mix = new _Mixture(eps); - for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); - return mix.buildPMF(); - } -}; - -// src/common/errors.ts -var DiceParseError = class _DiceParseError extends Error { - constructor(message, options) { - super(message); - this.name = "DiceParseError"; - this.expression = options?.expression; - this.cause = options?.cause; - Object.setPrototypeOf(this, _DiceParseError.prototype); - } -}; - -// src/parser/dice.ts -var MAX_BINARY_OUTCOMES = 1e8; -var Dice = class _Dice { - constructor(x = 0) { - this.faces = {}; - this.privateData = {}; - // Partial: the object starts empty and gains keys as outcomes are recorded, - // so the type must not claim every OutcomeType is present. (Previously typed - // as a full Record via an `as` cast, which lied about missing keys.) - this.outcomeData = {}; - this.hasHitDistributionCalculated = false; - if (x <= 0) return; - for (let i = 1; i <= x; i++) { - this.faces[i] = 1; - } - } - getOutcomeDistribution(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const distribution = this.outcomeData[key]; - if (distribution === void 0) return void 0; - return { ...distribution }; - } - getFullOutcomeDistribution() { - return { ...this.outcomeData }; - } - setOutcomeDistribution(key, data) { - if (data) { - this.outcomeData[key] = data; - } else { - delete this.outcomeData[key]; - } - } - hasOutcomeData(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const data = this.outcomeData[key]; - return data !== void 0 && Object.keys(data).length > 0; - } - getOutcomeCount(key, face) { - return this.outcomeData[key]?.[face] ?? 0; - } - getAverage(key) { - const distribution = this.getOutcomeDistribution(key); - if (!distribution) return 0; - const totalCount = Object.values(distribution).reduce( - (sum, count) => sum + count, - 0 - ); - const expectedDamage = Object.entries(distribution).reduce( - (sum, [damage, count]) => sum + Number(damage) * count, - 0 - ); - if (totalCount === 0) return 0; - return expectedDamage / totalCount; - } - // TODO this can be private later if we change how testing works - calculateHitDistribution() { - const hitValues = {}; - const subtractedOutcomes = [ - this.outcomeData.crit, - this.outcomeData.missNone, - this.outcomeData.missDamage, - this.outcomeData.saveHalf, - this.outcomeData.saveFail, - this.outcomeData.pc - ]; - for (const [face, totalCount] of Object.entries(this.faces)) { - const numFace = Number(face); - let hitCount = totalCount; - for (const distribution of subtractedOutcomes) { - const outcomeCount = distribution?.[numFace]; - if (outcomeCount) { - hitCount -= outcomeCount; - } - } - if (numFace === 0) { - hitCount = 0; - } - if (hitCount < 0) { - hitCount = 0; - } - hitValues[numFace] = hitCount; - } - return hitValues; - } - ensureHitDistribution() { - if (!this.hasHitDistributionCalculated) { - const hitValues = this.calculateHitDistribution(); - this.setOutcomeDistribution("hit", hitValues); - this.hasHitDistributionCalculated = true; - } - } - // PRIVATE FUNCTIONS - binaryOp(other, op, diceConstructor) { - const result = diceConstructor ? diceConstructor() : new _Dice(); - const isScalar = typeof other === "number"; - const keys1 = this.keys(); - const keys2 = isScalar ? [] : other.keys(); - if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { - throw new DiceParseError( - `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` - ); - } - for (const key1 of keys1) { - const value1 = this.faces[key1]; - if (isScalar) { - const resultKey = op(key1, other); - result.increment(resultKey, value1); - } else { - for (const key2 of keys2) { - const value2 = other.faces[key2]; - const resultKey = op(key1, key2); - result.increment(resultKey, value1 * value2); - } - } - } - return result; - } - removeFaces(facesToRemove) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (!facesToRemove.includes(numKey)) { - result.faces[numKey] = value; - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // PUBLIC FUNCTIONS - getFaceEntries() { - return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); - } - getFaceMap() { - return { ...this.faces }; - } - get(face) { - return this.faces[face] ?? 0; - } - keys() { - return Object.keys(this.faces).map(Number); - } - values() { - return Object.values(this.faces); - } - total() { - return Object.values(this.faces).reduce((sum, value) => sum + value, 0); - } - setFace(key, value) { - this.faces[key] = value; - } - static scalar(value) { - const result = new _Dice(); - result.increment(value, 1); - return result; - } - maxFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.max(...numericKeys); - } - minFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.min(...numericKeys); - } - increment(face, count) { - const current = this.faces[face] || 0; - this.faces[face] = current + count; - } - normalize(scalar) { - const result = new _Dice(); - for (const [face, count] of Object.entries(this.faces)) { - result.faces[Number(face)] = count * scalar; - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // OPERATIONS - add(other) { - return this.binaryOp(other, (a, b) => a + b); - } - subtract(other) { - return this.binaryOp(other, (a, b) => a - b); - } - conditionalApply(other) { - return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); - } - multiply(other) { - return this.binaryOp(other, (a, b) => a * b); - } - addNonZero(other) { - return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); - } - eq(other) { - return this.binaryOp(other, (a, b) => a === b ? 1 : 0); - } - max(other) { - return this.binaryOp(other, (a, b) => Math.max(a, b)); - } - min(other) { - return this.binaryOp(other, (a, b) => Math.min(a, b)); - } - advantage() { - return this.max(this); - } - ge(other) { - return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); - } - divide(other) { - return this.binaryOp(other, (a, b) => a / b); - } - divideRoundUp(other) { - return this.binaryOp(other, (a, b) => Math.ceil(a / b)); - } - divideRoundDown(other) { - return this.binaryOp(other, (a, b) => Math.floor(a / b)); - } - and(other) { - return this.binaryOp(other, (a, b) => a && b ? 1 : 0); - } - checkTarget(other, comparisonLogic) { - const createResult = () => { - const result = new _Dice(); - result.increment(0, 0); - result.increment(1, 0); - return result; - }; - return this.binaryOp(other, comparisonLogic, createResult); - } - dc(other) { - const dcCheck = (a, b) => a >= b ? 0 : 1; - const result = this.checkTarget(other, dcCheck); - result.privateData.isDCCheck = true; - return result; - } - ac(other) { - const acCheck = (a, b) => a >= b ? a : 0; - return this.checkTarget(other, acCheck); - } - deleteFace(face) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (numKey !== face) { - result.increment(numKey, value); - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - reroll(toReroll) { - const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; - const rerollKeys = rerollDice.keys(); - const rerollSet = new Set(rerollKeys); - const removed = this.removeFaces(rerollKeys); - let result = new _Dice(); - for (const face of this.keys()) { - const wasRerolled = rerollSet.has(face); - result = result.combine(removed); - if (wasRerolled) { - result = result.combine(this); - } - } - return result; - } - // This is not addition and not rolling two dice at once. - // Instead, it’s mixing two distributions into a single weighted die. - combine(other) { - if (typeof other === "number") { - other = _Dice.scalar(other); - } - const result = new _Dice(); - for (const [key, value] of Object.entries(other.faces)) { - result.faces[Number(key)] = value; - } - const except = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - result.increment(numKey, value); - if (!(numKey in other.faces)) { - except.increment(numKey, value); - } - } - result.privateData = { ...this.privateData, except: other }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - combineInPlace(other) { - for (const [key, value] of Object.entries(other.faces)) { - const numKey = Number(key); - const current = this.faces[numKey] || 0; - this.faces[numKey] = current + value; - } - } - percent() { - const total = this.total(); - const result = {}; - for (const [face, count] of Object.entries(this.faces)) { - result[Number(face)] = count / total; - } - return result; - } - average() { - const total = this.total(); - if (total === 0) return 0; - let sum = 0; - for (const [key, value] of Object.entries(this.faces)) { - sum += Number(key) * value; - } - return sum / total; - } - /* - * Convert dice to PMF using OutcomeType labels directly from damage distribution. - * This is much cleaner than the original complex distribution conversion. - */ - toPMF(numEpsilon = EPS) { - const total = this.total(); - if (total === 0) return PMF.empty(numEpsilon); - this.ensureHitDistribution(); - const map = /* @__PURE__ */ new Map(); - const hitDistro = this.getOutcomeDistribution("hit") || {}; - const critDistro = this.getOutcomeDistribution("crit") || {}; - const missDistro = this.getOutcomeDistribution("missDamage") || {}; - const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; - const pcDistro = this.getOutcomeDistribution("pc") || {}; - const isSaveHalf = Object.keys(saveDistro).length > 0; - const isDCCheck = this.privateData.isDCCheck === true; - const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; - for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { - const face = Number(faceStr); - const faceCount = Number(faceCountRaw); - if (faceCount <= 0) continue; - let p = faceCount / total; - p = clampNonNeg(p); - if (!(p > 0)) continue; - if (numEpsilon >= 0 && p < numEpsilon) continue; - const count = {}; - const attr = {}; - if (hitDistro[face]) { - const c = clampNonNeg(hitDistro[face] / total); - if (c > 0) { - if (isSaveHalf || isDCCheck) { - count.saveFail = c; - attr.saveFail = clampNonNeg(face * hitDistro[face] / total); - } else { - count.hit = c; - attr.hit = clampNonNeg(face * hitDistro[face] / total); - } - } - } - if (critDistro[face]) { - const c = clampNonNeg(critDistro[face] / total); - if (c > 0) { - count.crit = c; - attr.crit = clampNonNeg(face * critDistro[face] / total); - } - } - if (missDistro[face]) { - const c = clampNonNeg(missDistro[face] / total); - if (c > 0) { - count.missDamage = c; - attr.missDamage = clampNonNeg(face * missDistro[face] / total); - } - } - if (saveDistro[face]) { - const c = clampNonNeg(saveDistro[face] / total); - if (c > 0) { - if (isSaveHalf) { - count.saveHalf = c; - attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); - } else { - count.saveFail = (count.saveFail ?? 0) + c; - attr.saveFail = clampNonNeg( - (attr.saveFail ?? 0) + face * saveDistro[face] / total - ); - } - } - } - if (pcDistro[face]) { - const c = clampNonNeg(pcDistro[face] / total); - if (c > 0) { - count.pc = c; - attr.pc = clampNonNeg(face * pcDistro[face] / total); - } - } - if (!isSaveHalf && !isDCCheck) { - const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); - const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); - if (unaccountedCount > 0) { - const frac = clampNonNeg(unaccountedCount / total); - if (frac > 0) { - count.missNone = (count.missNone ?? 0) + frac; - } - } - } - const bin = { p, count }; - if (Object.keys(attr).length > 0) { - bin.attr = attr; - } - map.set(face, bin); - } - const identifier = this.identifier || "ERROR"; - return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); - } -}; - -// src/parser/parser.ts -var MAX_DIE_SIDES = 1e6; -var MAX_DICE_COUNT = 1e4; -var MAX_KEEP_OUTCOMES = 1e6; -var parseCache = new LRUCache(1e3); -function parse(expression, n = 0) { - const cleaned = expression.replace(/ /g, "").toLowerCase(); - { - const cacheKey = `${cleaned}:${n}`; - const cached = parseCache.get(cacheKey); - if (cached) return cached; - } - const chars = [...cleaned]; - let result; - try { - result = parseExpression(chars, n); - } catch (error) { - throw new DiceParseError( - `Cannot parse dice expression [${expression}]: ${error}`, - { expression, cause: error } - ); - } - result.privateData = result.privateData || {}; - result.identifier = cleaned; - if (chars.length > 0) { - throw new DiceParseError( - `Unexpected token: '${chars[0]}' from expression: '${expression}'`, - { expression } - ); - } - const resultPMF = result.toPMF(-1); - { - const cacheKey = `${cleaned}:${n}`; - parseCache.set(cacheKey, resultPMF); - } - return resultPMF; -} -function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { - dice = dice.normalize(currentNorm); - finalResult = finalResult.normalize(normValue); - finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); - finalResult = finalResult.combine(dice); - return { newNorm: currentNorm * normValue, updatedResult: finalResult }; -} -function parseExpression(arr, n) { - const result = (() => { - const res = parseArgument(arr, n); - return typeof res === "number" ? Dice.scalar(res) : res; - })(); - let op = parseOperation(arr); - let finalResult = result; - while (op != null) { - const arg = !op.unary ? parseArgument(arr, n) : finalResult; - let crit; - let critNorm = 1; - if (arr[0] === "x" || arr[0] === "c") { - const isXcrit = arr[0] === "x"; - if (isXcrit) assertToken(arr, "x"); - assertToken(arr, "c"); - assertToken(arr, "r"); - assertToken(arr, "i"); - assertToken(arr, "t"); - const count = isXcrit ? parseNumber(arr, n) : 1; - crit = new Dice(); - for (let i = 0; i < count; i++) { - const max = finalResult.maxFace(); - crit.setFace(max, finalResult.get(max)); - finalResult = finalResult.deleteFace(max); - } - critNorm = crit.total(); - crit = op.call(crit, parseBinaryArgument(arg, arr, n)); - critNorm = crit && critNorm ? crit.total() / critNorm : 1; - } - let save; - let saveNorm = 1; - if (arr[0] === "s") { - assertToken(arr, "s"); - assertToken(arr, "a"); - assertToken(arr, "v"); - assertToken(arr, "e"); - save = new Dice(); - const min = finalResult.minFace(); - save.increment(min > 0 ? min : 1, finalResult.get(min)); - saveNorm = save.total(); - finalResult = finalResult.deleteFace(min); - save = op.call(save, parseBinaryArgument(arg, arr, n)); - saveNorm = save && saveNorm ? save.total() / saveNorm : 1; - } - let pc; - let pcNorm = 1; - if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { - assertToken(arr, "p"); - assertToken(arr, "c"); - pc = new Dice(); - const min = finalResult.minFace(); - pc.increment(min > 0 ? min : 1, finalResult.get(min)); - const missBefore = pc.total(); - finalResult = finalResult.deleteFace(min); - pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); - const missAfter = pc ? pc.total() : 0; - pcNorm = missBefore ? missAfter / missBefore : 1; - } - let miss; - let missNorm = 1; - if (arr[0] === "m") { - assertToken(arr, "m"); - assertToken(arr, "i"); - assertToken(arr, "s"); - assertToken(arr, "s"); - miss = new Dice(); - const min = finalResult.minFace(); - miss.increment(min > 0 ? min : 1, finalResult.get(min)); - missNorm = miss.total(); - finalResult = finalResult.deleteFace(min); - miss = op.call(miss, parseBinaryArgument(arg, arr, n)); - missNorm = miss && missNorm ? miss.total() / missNorm : 1; - } - let norm = finalResult.total(); - finalResult = op.call(finalResult, arg); - norm = norm ? finalResult.total() / norm : 1; - if (crit) { - const result2 = combineDiceWithNormalization( - crit, - critNorm, - "crit", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (save) { - const result2 = combineDiceWithNormalization( - save, - saveNorm, - "saveHalf", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (miss) { - const result2 = combineDiceWithNormalization( - miss, - missNorm, - "missDamage", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (pc) { - const result2 = combineDiceWithNormalization( - pc, - pcNorm, - "pc", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - op = parseOperation(arr); - } - return finalResult; -} -function parseArgument(s, n) { - let result = parseArgumentInternal(s, n); - while (true) { - const next = parseArgumentInternal(s, n); - if (next === void 0) break; - result = multiplyDiceByDice(result, next); - } - return result; -} -function multiplyDiceByDice(d1, d2) { - if (typeof d1 === "number") d1 = Dice.scalar(d1); - if (typeof d2 === "number") d2 = Dice.scalar(d2); - const result = new Dice(); - const faces = /* @__PURE__ */ new Map(); - let normalizationFactor = 1; - for (const key of d1.keys()) { - let face; - if (typeof key !== "number") { - continue; - } - if (d2.privateData.keep) { - const faceCount = d2.keys().length; - if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { - throw new DiceParseError( - `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` - ); - } - const repeat = Array(key).fill(d2); - face = opDice(repeat, d2.privateData.keep); - } else { - face = multiplyDice(key, d2); - } - normalizationFactor *= face.total(); - faces.set(key, face); - } - for (const [k, face] of faces) { - const count = d1.get(k); - result.combineInPlace( - face.normalize(count * normalizationFactor / face.total()) - ); - } - result.privateData.except = {}; - return result; -} -function multiplyDice(n, d2) { - if (n > MAX_DICE_COUNT) { - throw new DiceParseError( - `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` - ); - } - if (n === 0) return new Dice(0); - if (n === 1) return d2; - const half = Math.floor(n / 2); - let result = multiplyDice(half, d2); - result = result.add(result); - if (n % 2 === 1) { - result = result.add(d2); - } - return result; -} -function opDice(diceList, keepFn) { - return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); -} -function opDiceInternal(diceList, result, index, values, weight, combineFn) { - if (index === diceList.length) { - return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); - } - const currentDice = diceList[index]; - for (const face of currentDice.keys()) { - values.push(face); - result = opDiceInternal( - diceList, - result, - index + 1, - values, - weight * currentDice.get(face), - combineFn - ); - values.pop(); - } - return result; -} -function parseArgumentInternal(s, n) { - if (s.length === 0) return; - const c = s[0]; - switch (c) { - case "(": - s.shift(); - return assertToken(s, ")", parseExpression(s, n)); - case "h": - case "d": - return parseDice(s, n); - case "k": - assertToken(s, "k"); - return parseKeep(s, n); - case "n": - return parseNumber(s, n); - default: - if (isDigit(c)) return parseNumber(s, n); - return; - } -} -function parseBinaryArgument(arg, arr, n) { - if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { - assertToken(arr, "half"); - const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; - return diceArg.divideRoundDown(2); - } - const parsed = parseArgument(arr, n); - return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; -} -function assertToken(s, expected, ret) { - for (const ch of expected) { - const found = s.shift(); - if (found !== ch) { - throw new Error(`Expected character '${ch}', found '${found}'`); - } - } - return ret; -} -function parseDice(s, n) { - let rerollOne = false; - if (peek(s, "hd") && peekIsNumber(s, 2)) { - assertToken(s, "h"); - assertToken(s, "d"); - rerollOne = true; - } else if (peek(s, "d") && peekIsNumber(s, 1)) { - assertToken(s, "d"); - } else { - return; - } - const sides = parseNumber(s, n); - if (sides > MAX_DIE_SIDES) { - throw new DiceParseError( - `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` - ); - } - let result = new Dice(sides); - if (rerollOne) { - result = result.reroll(1); - } - return result; -} -function peek(arr, expected) { - if (expected.length > arr.length) return false; - for (let i = 0; i < expected.length; i++) { - if (arr[i] !== expected.charAt(i)) return false; - } - return true; -} -function peekIsNumber(arr, index) { - if (index >= arr.length) return false; - return isDigit(arr[index]) || arr[index] === "n"; -} -function parseNumber(s, n) { - let ret = ""; - while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { - const ch = s.shift(); - ret += ch === "n" ? n.toString() : ch; - } - if (ret.length === 0) { - throw new Error(`Expected number, found: '${s[0]}'`); - } - return parseInt(ret, 10); -} -function isDigit(c) { - return c >= "0" && c <= "9"; -} -function parseKeep(s, n) { - let keepLowest = false; - if (peek(s, "l")) { - assertToken(s, "l"); - keepLowest = true; - } else if (peek(s, "h")) { - assertToken(s, "h"); - keepLowest = false; - } else { - return; - } - const keepCount = parseNumber(s, n); - const result = parseArgumentInternal(s, n); - if (result instanceof Dice) { - result.privateData.keep = keepN(keepCount, keepLowest); - return result; - } - throw new Error("Expected Dice after keep modifier"); -} -function keepN(n, low) { - return (values) => { - const sorted = [...values].sort((a, b) => low ? a - b : b - a); - return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); - }; -} -function parseOperation(s) { - switch (s[0]) { - case ")": - return; - case "a": - assertToken(s, "ac"); - return Dice.prototype.ac; - case "d": - assertToken(s, "dc"); - return Dice.prototype.dc; - case "!": - assertToken(s, "!"); - const adv = Dice.prototype.advantage; - adv.unary = true; - return adv; - case ">": - assertToken(s, ">"); - return Dice.prototype.max; - case "<": - assertToken(s, "<"); - return Dice.prototype.min; - case "+": - assertToken(s, "+"); - return Dice.prototype.addNonZero; - case "~": - assertToken(s, "~"); - assertToken(s, "+"); - return Dice.prototype.add; - case "-": - assertToken(s, "-"); - return Dice.prototype.subtract; - case "&": - assertToken(s, "&"); - return Dice.prototype.combine; - case "r": - assertToken(s, "reroll"); - return Dice.prototype.reroll; - case "*": - assertToken(s, "*"); - if (peek(s, "*")) { - assertToken(s, "*"); - return Dice.prototype.multiply; - } - return Dice.prototype.conditionalApply; - case "/": - assertToken(s, "/"); - if (s[0] === "/") { - assertToken(s, "/"); - return Dice.prototype.divideRoundDown; - } - return Dice.prototype.divideRoundUp; - case "=": - assertToken(s, "="); - return Dice.prototype.eq; - } - return; -} - -// src/builder/prob.ts -function d20PmfFromCdf(cdfPow, eps = EPS) { - const out = /* @__PURE__ */ new Map(); - let prev = 0; - for (let k = 1; k <= 20; k++) { - const cur = cdfPow(k); - const pk = cur - prev; - if (pk > 0) { - out.set(k, pk); - } - prev = cur; - } - return PMF.fromMap(out, eps); -} - -// src/builder/d20.ts -var cacheKeyMap = { - "flat-flat": "d20", - "flat-reroll": "hd20", - "advantage-flat": "d20 > d20", - "advantage-reroll": "hd20 > hd20", - "disadvantage-flat": "d20 < d20", - "disadvantage-reroll": "hd20 < hd20", - "elven accuracy-flat": "d20 > d20 > d20", - "elven accuracy-reroll": "hd20 > hd20 > hd20" -}; -function d20RollPMF(rollType, rerollOne = false) { - rollType = rollType || "flat"; - const cacheKeyLookup = `${rollType}-${rerollOne ? "reroll" : "flat"}`; - const cacheKey = cacheKeyMap[cacheKeyLookup]; - if (!cacheKey) { - throw new Error(`Invalid roll type: ${rollType}`); - } - const cached = pmfCache.get(cacheKey); - if (cached) return cached; - const base = d20PMF(rerollOne); - if (!rollType || rollType === "flat") { - pmfCache.set(cacheKey, base); - return base; - } - const p = new Array(21).fill(0); - for (const [r, rec] of base) { - const pr = typeof rec === "number" ? rec : rec.p; - if (r >= 1 && r <= 20) p[r] = pr; - } - const F = new Array(21).fill(0); - for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k]; - const eps = 0; - let result = base; - if (rollType === "advantage") { - result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps); - } else if (rollType === "elven accuracy") { - result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps); - } else if (rollType === "disadvantage") { - result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps); - } - pmfCache.set(cacheKey, result); - return result; -} -function d20PMF(rerollOne) { - const cacheKey = `flat-${rerollOne ? "reroll" : "flat"}`; - const cached = pmfCache.get(cacheKey); - if (cached) return cached; - const m = /* @__PURE__ */ new Map(); - const base = 1 / 20; - const rerollShare = base * base; - if (!rerollOne) { - for (let r = 1; r <= 20; r++) { - m.set(r, base); - } - } else { - for (let r = 1; r <= 20; r++) { - m.set(r, (r === 1 ? 0 : base) + rerollShare); - } - } - const result = PMF.fromMap(m, EPS); - pmfCache.set(cacheKey, result); - return result; -} - -// src/builder/roll.ts -var defaultConfig = { - count: 1, - sides: 0, - modifier: 0, - reroll: 0, - explode: 0, - minimum: 0, - bestOf: 0, - keep: void 0, - rollType: "flat" -}; -var rollConfigsEqual = (a, b) => { - return a.count === b.count && a.sides === b.sides && a.modifier === b.modifier && a.reroll === b.reroll && a.explode === b.explode && a.minimum === b.minimum && a.bestOf === b.bestOf && a.keep === b.keep && a.rollType === b.rollType; -}; -var configComplexityScore = (config) => { - return (config.reroll > 0 ? 1 : 0) + (config.explode > 0 ? 1 : 0) + (config.minimum > 0 ? 1 : 0) + (config.bestOf > 0 ? 1 : 0) + (config.keep !== void 0 ? 1 : 0) + (config.rollType !== "flat" ? 1 : 0); -}; -var RollBuilder = class _RollBuilder { - constructor(countOrConfigs = 1) { - // --- Dice Shortcut Methods --- - this.d4 = () => this.d(4); - this.d6 = () => this.d(6); - this.d8 = () => this.d(8); - this.d10 = () => this.d(10); - this.d12 = () => this.d(12); - this.d20 = () => this.d(20); - this.d100 = () => this.d(100); - if (typeof countOrConfigs === "number") { - const count = countOrConfigs; - if (isNaN(count)) throw new Error("Invalid NaN value for count"); - this.subRollConfigs = [ - { ...defaultConfig, count, isSubtraction: count < 0 } - ]; - } else { - this.subRollConfigs = countOrConfigs.map((c) => ({ ...c })); - } - } - create(configs) { - return new _RollBuilder(configs); - } - get lastConfig() { - return this.subRollConfigs[this.subRollConfigs.length - 1]; - } - hasHiddenState() { - return false; - } - getSubRollConfigs() { - return this.subRollConfigs.map((c) => ({ ...c })); - } - // for testing - static fromConfig(config) { - return new _RollBuilder([{ ...defaultConfig, ...config }]); - } - static fromConfigs(configs) { - return new _RollBuilder( - configs.map((config) => ({ ...defaultConfig, ...config })) - ); - } - static fromArgs(...args) { - if (args.length === 1) { - const arg = args[0]; - if (typeof arg === "number") { - if (isNaN(arg)) throw new Error("Invalid NaN value for argument"); - return new _RollBuilder(0).plus(arg); - } - if (typeof arg === "string") { - return new ParsedRollBuilder(arg); - } - if (arg instanceof _RollBuilder) { - return arg; - } - } - if (args.length === 2 || args.length === 3) { - const [count, sidesOrDie, modifier] = args; - if (typeof count !== "number") { - throw new Error("First argument must be a number for multi-arg call"); - } - if (isNaN(count)) throw new Error("Invalid NaN value for count argument"); - if (sidesOrDie instanceof _RollBuilder) { - if (sidesOrDie.hasHiddenState()) { - throw new Error( - "Cannot use a roll with hidden state (like a pooled roll) as a die type." - ); - } - const subRollConfigs = sidesOrDie.getSubRollConfigs(); - if (subRollConfigs.length === 0) { - const result = new _RollBuilder(0); - return modifier !== void 0 ? result.plus(modifier) : result; - } - const absCount = Math.abs(count); - const newConfigs = subRollConfigs.map((config) => ({ - ...config, - count: config.count * absCount, - modifier: config.modifier * absCount - })); - let resultBuilder = new _RollBuilder(newConfigs); - if (count < 0) { - const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); - resultBuilder = new _RollBuilder(negatedConfigs); - } - return modifier !== void 0 ? resultBuilder.plus(modifier) : resultBuilder; - } else if (typeof sidesOrDie === "number" || sidesOrDie === void 0) { - if (typeof sidesOrDie === "number" && isNaN(sidesOrDie)) - throw new Error("Invalid NaN value for sides argument"); - let builder = new _RollBuilder(count); - if (sidesOrDie && sidesOrDie > 0) { - builder = builder.d(sidesOrDie); - } - return modifier !== void 0 ? builder.plus(modifier) : builder; - } - } - throw new Error(`Invalid arguments passed: ${args.join(", ")}`); - } - // --- Core Dice Methods --- - d(sides) { - if (sides !== void 0 && isNaN(sides)) - throw new Error("Invalid NaN value for sides"); - if (sides === void 0) return this; - if (this.lastConfig.sides && this.lastConfig.sides > 0) { - throw new Error("Cannot add a die after adding a die"); - } - if (sides === 0) return this; - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].sides = sides; - return this.create(newConfigs); - } - plus(modOrRoll, die) { - if (typeof modOrRoll === "number" && isNaN(modOrRoll)) - throw new Error("Invalid NaN value for modOrRoll"); - if (die instanceof _RollBuilder && typeof modOrRoll === "number") { - if (die.hasHiddenState()) { - throw new Error( - "Cannot use a roll with hidden state (like a pooled roll) as a die type." - ); - } - const count = modOrRoll; - const subRollConfigs = die.getSubRollConfigs(); - if (subRollConfigs.length === 0) return this; - const absCount = Math.abs(count); - const newConfigs = subRollConfigs.map((config) => ({ - ...config, - count: config.count * absCount, - modifier: config.modifier * absCount - })); - let rollToAdd = new _RollBuilder(newConfigs); - if (count < 0) { - const negatedConfigs = rollToAdd.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); - rollToAdd = new _RollBuilder(negatedConfigs); - } - return this.add(rollToAdd); - } - if (die !== void 0) { - throw new Error("Invalid arguments to plus()"); - } - if (modOrRoll === void 0) return this; - if (typeof modOrRoll === "number") { - if (modOrRoll === 0) return this; - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].modifier += modOrRoll; - return this.create(newConfigs); - } - return this.add(modOrRoll); - } - minus(modOrRoll, die) { - const isNumber = typeof modOrRoll === "number"; - const dieIsRoll = die instanceof _RollBuilder; - if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die); - if (die !== void 0) throw new Error("Invalid arguments to minus()"); - if (modOrRoll === void 0) return this; - return isNumber ? this.plus(-modOrRoll) : this.plus(-1, modOrRoll); - } - /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */ - reroll(value) { - if (isNaN(value)) throw new Error("Invalid NaN value for reroll"); - if (value === this.lastConfig.reroll) return this; - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].reroll = value; - return this.create(newConfigs); - } - /** Set finite explode count for max-face explosions (Infinity allowed). */ - explode(count = Infinity) { - if (count !== void 0 && isNaN(count)) - throw new Error("Invalid NaN value for explode count"); - if (count === void 0) return this; - if (count === 0) return this; - if (count < 0) throw new Error("Explode count must be >= 0"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].explode = count; - return this.create(newConfigs); - } - /** Apply per-die minimum value (min > 0). */ - minimum(val) { - if (val !== void 0 && isNaN(val)) - throw new Error("Invalid NaN value for minimum"); - if (val === void 0) return this; - if (val === 0) return this; - if (val < 0) throw new Error("Minimum value must be >= 0"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].minimum = val + 1; - return this.create(newConfigs); - } - bestOf(count) { - if (count !== void 0 && isNaN(count)) - throw new Error("Invalid NaN value for bestOf count"); - if (count === void 0) return this; - if (count <= 0) throw new Error("Best of count must be > 0"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].bestOf = count; - return this.create(newConfigs); - } - keepHighest(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepHighest"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].keep = { total, count, mode: "highest" }; - return this.create(newConfigs); - } - keepLowest(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepLowest"); - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].keep = { total, count, mode: "lowest" }; - return this.create(newConfigs); - } - keepHighestAll(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepHighestAll"); - const currentAST = this.toAST(); - const trialPool = { - type: "sum", - count: total, - child: currentAST - }; - const keepNode = { - type: "keep", - mode: "highest", - count, - child: trialPool - }; - const currentExpr = this.toExpression(); - const expression = `${total}kh${count}(${currentExpr})`; - return new PooledRollBuilder(keepNode, expression); - } - keepLowestAll(total, count) { - if (isNaN(total) || isNaN(count)) - throw new Error("Invalid NaN value for keepLowestAll"); - const currentAST = this.toAST(); - const trialPool = { - type: "sum", - count: total, - child: currentAST - }; - const keepNode = { - type: "keep", - mode: "lowest", - count, - child: trialPool - }; - const currentExpr = this.toExpression(); - const expression = `${total}kl${count}(${currentExpr})`; - return new PooledRollBuilder(keepNode, expression); - } - withAdvantage() { - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].rollType = "advantage"; - return this.create(newConfigs); - } - withDisadvantage() { - const configs = this.getSubRollConfigs(); - configs[configs.length - 1].rollType = "disadvantage"; - return this.create(configs); - } - add(anotherRoll) { - if (anotherRoll === void 0) return this; - if (anotherRoll.hasHiddenState()) { - throw new Error( - "Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll)." - ); - } - const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; - return this.create(configs); - } - withBonus(anotherRoll) { - const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs]; - return this.create(configs); - } - addRoll(count = 1) { - if (isNaN(count)) throw new Error("Invalid NaN value for count"); - const configs = [ - ...this.subRollConfigs, - { - ...defaultConfig, - count, - isSubtraction: count < 0 - } - ]; - return this.create(configs); - } - scaleDice(scale) { - const scaleInt = Math.floor(scale); - if (scaleInt !== scale) throw new Error("Scale must be an integer"); - if (scaleInt <= 0) throw new Error("Scale must be > 0"); - const newConfigs = this.getSubRollConfigs().map((config) => { - if (!config.sides || config.sides <= 0) return config; - return { ...config, count: config.count * scaleInt }; - }); - return this.create(newConfigs); - } - doubleDice() { - return this.scaleDice(2); - } - alwaysHits() { - return new AlwaysHitBuilder(this); - } - alwaysCrits() { - return new AlwaysCritBuilder(this); - } - copy() { - return this.create(this.getSubRollConfigs()); - } - withElvenAccuracy() { - const newConfigs = this.getSubRollConfigs(); - newConfigs[newConfigs.length - 1].rollType = "elven accuracy"; - return this.create(newConfigs); - } - toExpression() { - const originalDiceConfigs = this.subRollConfigs.filter( - (config) => config.sides && config.sides > 0 - ); - const configGroups = /* @__PURE__ */ new Map(); - for (const config of originalDiceConfigs) { - const keyConfig = { ...config }; - delete keyConfig.count; - delete keyConfig.modifier; - const key = JSON.stringify(keyConfig); - const existingGroup = configGroups.get(key); - if (existingGroup) { - existingGroup.totalCount += config.count; - } else { - configGroups.set(key, { config, totalCount: config.count }); - } - } - const rootConfig = this.getRootDieConfig(); - const groupedConfigs = Array.from(configGroups.values()); - let rootD20Group; - if (rootConfig && rootConfig.sides === 20) { - const rootIndex = groupedConfigs.findIndex( - ({ config }) => rollConfigsEqual(config, rootConfig) && JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep) - ); - if (rootIndex !== -1) { - rootD20Group = groupedConfigs.splice(rootIndex, 1)[0]; - } - } - const sortedDiceConfigs = groupedConfigs.map(({ config, totalCount }) => ({ - ...config, - count: totalCount - })).sort((a, b) => { - const aHasPriority = a.reroll > 0 || a.minimum > 0; - const bHasPriority = b.reroll > 0 || b.minimum > 0; - if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1; - if (b.sides !== a.sides) return b.sides - a.sides; - return configComplexityScore(b) - configComplexityScore(a); - }); - const diceConfigs = rootD20Group ? [ - { ...rootD20Group.config, count: rootD20Group.totalCount }, - ...sortedDiceConfigs - ] : sortedDiceConfigs; - const totalModifier = this.subRollConfigs.reduce( - (sum, config) => sum + config.modifier, - 0 - ); - if (diceConfigs.length === 0) return totalModifier.toString(); - const rootDieConfig = this.getRootDieConfig(); - const newRootConfig = rootDieConfig ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig)) : void 0; - const diceExpressions = diceConfigs.map( - (config) => this.configToSingleExpressionWithoutModifier( - config, - config === newRootConfig - ) - ); - let result = ""; - for (let i = 0; i < diceExpressions.length; i++) { - const config = diceConfigs[i]; - const expression = diceExpressions[i]; - if (i === 0) { - result = (config.isSubtraction ? "-" : "") + expression; - if (config.sides === 20 && totalModifier !== 0) { - if (totalModifier > 0) result += ` + ${totalModifier}`; - else result += ` - ${Math.abs(totalModifier)}`; - } - } else { - const operator = config.isSubtraction ? " - " : " + "; - result += operator + expression; - } - } - if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) { - if (totalModifier > 0) result += ` + ${totalModifier}`; - else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`; - } - return result.replace(/\+ -/g, "-"); - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - get pmf() { - return this.toPMF(); - } - toQuery(eps = 0) { - return this.toPMF(eps).query(); - } - toAST() { - const configs = this.getSubRollConfigs(); - return astFromRollConfigs(configs) || { type: "constant", value: 0 }; - } - configToSingleExpressionWithoutModifier(config, isRootDie) { - if (!config.sides || config.sides <= 0) return ""; - let baseDie = `d${config.sides}`; - if (config.reroll > 0) { - if (config.minimum > 0 && config.explode > 0) ; else if (config.minimum > 0) { - for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`; - } else { - for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`; - } - } - if (config.minimum > 0) { - if (config.reroll > 0 && !config.explode) { - baseDie = `${config.minimum}>(${baseDie})`; - } else { - baseDie = `${config.minimum}>${baseDie}`; - } - if (config.reroll > 0 && config.explode > 0) { - for (let i = 1; i <= config.reroll; i++) { - baseDie += ` reroll ${i}`; - } - } - } - if (baseDie === "d20 reroll 1" && config.minimum <= 1) baseDie = "hd20"; - let mainExpression = ""; - switch (config.rollType) { - case "advantage": - mainExpression = `${baseDie} > ${baseDie}`; - break; - case "disadvantage": - mainExpression = `${baseDie} < ${baseDie}`; - break; - case "elven accuracy": - mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`; - break; - case "flat": - if (config.keep) { - const mode = config.keep.mode === "highest" ? "kh" : "kl"; - const baseDieExpression = this.configToSingleExpressionWithoutModifier( - { - ...config, - count: config.count, - modifier: 0, - rollType: "flat", - keep: void 0 - }, - false - ); - mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`; - } else { - const isComplex = baseDie.length > `d${config.sides}`.length; - const isHalflingShorthand = baseDie === "hd20"; - const isD20Shorthand = baseDie === "d20" && isRootDie; - const hasMinimum = config.minimum > 0; - const hasReroll = config.reroll > 0; - const effectiveCount = config.isSubtraction ? Math.abs(config.count) : config.count < 0 ? 1 : Math.abs(config.count); - if (effectiveCount > 1) { - const shouldAddParentheses = isComplex; - mainExpression = shouldAddParentheses ? `${effectiveCount}(${baseDie})` : `${effectiveCount}${baseDie}`; - } else if (effectiveCount === 1) { - const needsParens = hasReroll && hasMinimum; - if (config.isSubtraction) { - mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; - } else if (isComplex || isHalflingShorthand || isD20Shorthand || config.count < 0) { - mainExpression = needsParens ? `1(${baseDie})` : baseDie; - } else { - mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`; - } - } else { - mainExpression = baseDie; - } - } - if (config.bestOf && config.count && config.bestOf < config.count) { - mainExpression += `kh${config.bestOf}`; - } - break; - } - return mainExpression; - } - getRootDieConfig() { - const configs = this.subRollConfigs; - return configs.find((config) => config.sides > 0) || configs[0]; - } - getAllDieConfigs() { - return this.getSubRollConfigs(); - } - getBonusDiceConfigs() { - const allConfigs = this.subRollConfigs; - const rootConfig = allConfigs.find((config) => config.sides > 0) || allConfigs[0]; - if (!rootConfig) return []; - return allConfigs.filter((config) => config.sides > 0).filter((config) => config !== rootConfig); - } - getBonusDicePMFs(check, eps = 0) { - return check.getBonusDiceConfigs().map( - (config) => pmfFromRollBuilder(_RollBuilder.fromConfigs([config]), eps) - ); - } - get modifier() { - return this.subRollConfigs.reduce( - (sum, config) => sum + config.modifier, - 0 - ); - } - get rollType() { - const rootConfig = this.getRootDieConfig(); - return rootConfig?.rollType || "flat"; - } - get baseReroll() { - const rootConfig = this.getRootDieConfig(); - return rootConfig?.reroll || 0; - } - half() { - return new HalfRollBuilder(this); - } - /** - * Scale this roll's result by `numerator / denominator`, rounding each outcome. - * A general, composable form of {@link half} — used to model damage-type resistance - * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`). - * Compose several of these (and plain rolls) into one payload with {@link sumRolls}. - */ - scaleResult(numerator, denominator = 1, rounding = "floor") { - return new ScaleRollBuilder(this, numerator, denominator, rounding); - } - // Create a "max of N rolls" version of this roll for crit damage with keep operations - maxOf(count) { - return new MaxOfRollBuilder(this, count); - } - // These methods are implemented via prototype augmentation in ac.ts and dc.ts - // They are declared here to provide proper TypeScript types - ac(_targetAC) { - throw new Error("ac() should be implemented via prototype augmentation"); - } - dc(_saveDC) { - throw new Error("dc() should be implemented via prototype augmentation"); - } -}; -var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder { - constructor(innerRoll) { - super(0); - this.innerRoll = innerRoll; - } - hasHiddenState() { - return this.innerRoll.hasHiddenState(); - } - // No need to override create if we don't expose RollBuilder methods that use it, - // but HalfRollBuilder extends RollBuilder so it does. - // However, HalfRollBuilder seems to just wrap another roll. - // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder? - // No, RollBuilder.plus returns RollBuilder. - // The inheritance here is a bit tricky. - // Existing code for HalfRollBuilder doesn't seem to implement plus/etc. - // So .plus() on a HalfRollBuilder would return a RollBuilder (base class). - // Which is fine. - // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that. - get lastConfig() { - return this.innerRoll.lastConfig; - } - getSubRollConfigs() { - return this.innerRoll.getSubRollConfigs(); - } - toExpression() { - const innerExpression = this.innerRoll.toExpression(); - return `(${innerExpression}) // 2`; - } - toAST() { - return { - type: "half", - child: this.innerRoll.toAST() - }; - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _HalfRollBuilder(this.innerRoll.copy()); - } -}; -var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder { - constructor(innerRoll, numerator, denominator = 1, rounding = "floor") { - super(0); - this.innerRoll = innerRoll; - this.numerator = numerator; - this.denominator = denominator; - this.rounding = rounding; - } - hasHiddenState() { - return this.innerRoll.hasHiddenState(); - } - get lastConfig() { - return this.innerRoll.lastConfig; - } - getSubRollConfigs() { - return this.innerRoll.getSubRollConfigs(); - } - toExpression() { - const inner = this.innerRoll.toExpression(); - if (this.denominator === 1) return `${this.numerator} * (${inner})`; - if (this.numerator === 1) return `(${inner}) // ${this.denominator}`; - return `(${inner}) * ${this.numerator} // ${this.denominator}`; - } - toAST() { - return { - type: "scale", - numerator: this.numerator, - denominator: this.denominator, - rounding: this.rounding, - child: this.innerRoll.toAST() - }; - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _ScaleRollBuilder( - this.innerRoll.copy(), - this.numerator, - this.denominator, - this.rounding - ); - } -}; -var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder { - constructor(innerRoll, count, diceCount, diceSides) { - super(0); - this.innerRoll = innerRoll; - this.count = count; - this.diceCount = diceCount; - this.diceSides = diceSides; - } - hasHiddenState() { - return this.innerRoll.hasHiddenState(); - } - get lastConfig() { - return this.innerRoll.lastConfig; - } - getSubRollConfigs() { - return this.innerRoll.getSubRollConfigs(); - } - toExpression() { - if (this.diceCount && this.diceSides) { - return `max${this.count}(${this.diceCount}d${this.diceSides})`; - } - return `max${this.count}(?d?)`; - } - toAST() { - if (this.diceCount && this.diceSides) { - const sumChild = { - type: "sum", - count: this.diceCount, - child: { type: "die", sides: this.diceSides } - }; - return { - type: "maxOf", - count: this.count, - child: sumChild - }; - } - try { - const configs = this.innerRoll.getSubRollConfigs(); - if (configs.length === 1 && configs[0].sides) { - const config = configs[0]; - const sumChild = { - type: "sum", - count: config.count, - child: { type: "die", sides: config.sides } - }; - return { - type: "maxOf", - count: this.count, - child: sumChild - }; - } - } catch { - } - throw new Error( - `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration` - ); - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count); - } -}; -var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder { - constructor(baseRoll, attackConfig) { - if (baseRoll.hasHiddenState()) { - throw new Error( - "Cannot create AlwaysHitBuilder from a roll with hidden state." - ); - } - super(baseRoll.getSubRollConfigs()); - if (attackConfig) { - this.attackConfig = { ...attackConfig }; - } else { - this.attackConfig = { critThreshold: 20 }; - } - } - create(configs) { - return new RollBuilder(configs); - } - onHit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new AttackBuilder(this, damageRoll); - } - get critThreshold() { - return this.attackConfig.critThreshold; - } - // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? - critOn(critThreshold) { - const newConfig = { critThreshold }; - return new _AlwaysHitBuilder(this, newConfig); - } - alwaysCrits() { - return new AlwaysCritBuilder(this, void 0, true); - } - // Legacy expressions - toExpression() { - const configs = this.getSubRollConfigs(); - return new RollBuilder(configs).toExpression(); - } - toPMF() { - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - return d20RollPMF(rollType, rerollOne); - } - copy() { - const baseCopy = new RollBuilder(this.getSubRollConfigs()); - const critThreshold = this.critThreshold; - const newConfig = { critThreshold }; - return new _AlwaysHitBuilder(baseCopy, newConfig); - } -}; -var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder { - constructor(baseRoll, attackConfig, fromAlwaysHit = false) { - if (baseRoll.hasHiddenState()) { - throw new Error( - "Cannot create AlwaysCritBuilder from a roll with hidden state." - ); - } - super(baseRoll.getSubRollConfigs()); - if (attackConfig) { - this.attackConfig = { ...attackConfig }; - } else { - this.attackConfig = { critThreshold: 20 }; - } - this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder; - } - create(configs) { - return new RollBuilder(configs); - } - onHit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new AttackBuilder(this, damageRoll); - } - get critThreshold() { - return this.attackConfig.critThreshold; - } - critOn(critThreshold) { - const newConfig = { critThreshold, ac: this.attackConfig.ac }; - return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit); - } - // Legacy expressions - toExpression() { - const configs = this.getSubRollConfigs(); - return new RollBuilder(configs).toExpression(); - } - toPMF() { - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - return d20RollPMF(rollType, rerollOne); - } - copy() { - const baseCopy = new RollBuilder(this.getSubRollConfigs()); - const critThreshold = this.critThreshold; - const newConfig = { critThreshold, ac: this.attackConfig.ac }; - return new _AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit); - } -}; -var ParsedRollBuilder = class _ParsedRollBuilder extends RollBuilder { - constructor(expression) { - super([]); - this.originalExpression = expression; - this.cachedPMF = parse(expression, 0); - } - hasHiddenState() { - return true; - } - create(configs) { - return new RollBuilder(configs); - } - toPMF(_eps = 0) { - return this.cachedPMF; - } - toExpression() { - return this.originalExpression; - } - toAST() { - throw new Error( - "ParsedRollBuilder does not support AST conversion. Use the builder API instead." - ); - } - copy() { - return new _ParsedRollBuilder(this.originalExpression); - } - doubleDice() { - throw new Error( - "ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead." - ); - } -}; -var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder { - constructor(baseAST, baseExpression, configs = []) { - super(configs.length > 0 ? configs : 0); - this.baseAST = baseAST; - this.baseExpression = baseExpression; - } - create(configs) { - return new _PooledRollBuilder(this.baseAST, this.baseExpression, configs); - } - hasHiddenState() { - return true; - } - d(_sides) { - throw new Error("Cannot add dice to a pooled roll. The pool is finalized."); - } - reroll(_value) { - throw new Error("Cannot set reroll on a pooled roll."); - } - explode(_count = Infinity) { - throw new Error("Cannot set explode on a pooled roll."); - } - minimum(_val) { - throw new Error("Cannot set minimum on a pooled roll."); - } - bestOf(_count) { - throw new Error("Cannot set bestOf on a pooled roll."); - } - keepHighest(_total, _count) { - throw new Error( - "Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling." - ); - } - keepLowest(_total, _count) { - throw new Error( - "Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling." - ); - } - withAdvantage() { - throw new Error("Cannot set advantage on a pooled roll."); - } - withDisadvantage() { - throw new Error("Cannot set disadvantage on a pooled roll."); - } - withElvenAccuracy() { - throw new Error("Cannot set elven accuracy on a pooled roll."); - } - toAST() { - const configsAST = super.toAST(); - const isZero = configsAST.type === "constant" && configsAST.value === 0; - if (isZero) { - return this.baseAST; - } - const children = [ - { node: this.baseAST, sign: 1 }, - { node: configsAST, sign: 1 } - ]; - return { type: "add", children }; - } - toExpression() { - const configsExpression = super.toExpression(); - if (configsExpression === "0") { - return this.baseExpression; - } - if (configsExpression.startsWith("-")) { - return `${this.baseExpression} - ${configsExpression.substring(1)}`; - } - return `${this.baseExpression} + ${configsExpression}`; - } - copy() { - return new _PooledRollBuilder( - this.baseAST, - this.baseExpression, - this.getSubRollConfigs() - ); - } - scaleDice(scale) { - const scaleInt = Math.floor(scale); - if (scaleInt !== scale) throw new Error("Scale must be an integer"); - if (scaleInt <= 0) throw new Error("Scale must be > 0"); - const newBaseAST = { - type: "sum", - count: scaleInt, - child: this.baseAST - }; - const newBaseExpr = scaleInt === 1 ? this.baseExpression : `${scaleInt}(${this.baseExpression})`; - return new _PooledRollBuilder( - newBaseAST, - newBaseExpr, - this.getSubRollConfigs() - ); - } - times(count) { - if (isNaN(count)) throw new Error("Invalid NaN value for times"); - if (Math.floor(count) !== count) - throw new Error("times() requires an integer"); - if (count < 0) throw new Error("times() requires a non-negative integer"); - const currentAST = this.toAST(); - const currentExpr = this.toExpression(); - const sumNode = { - type: "sum", - count, - child: currentAST - }; - const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`; - return new _PooledRollBuilder(sumNode, newExpr); - } -}; -var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder { - constructor(parts) { - super(0); - this.parts = parts; - } - hasHiddenState() { - return true; - } - getSubRollConfigs() { - return []; - } - toAST() { - return { - type: "add", - children: this.parts.map((p) => ({ - node: p.toAST(), - sign: 1 - })) - }; - } - toExpression() { - const exprs = this.parts.map((p) => p.toExpression()).filter((e) => e && e !== "0"); - if (exprs.length === 0) return "0"; - let result = exprs[0]; - for (let i = 1; i < exprs.length; i++) { - const e = exprs[i]; - result += e.startsWith("-") ? ` - ${e.substring(1)}` : ` + ${e}`; - } - return result.replace(/\+ -/g, "-"); - } - toPMF(eps = 0) { - return pmfFromRollBuilder(this, eps); - } - copy() { - return new _CompositeSumRollBuilder(this.parts.map((p) => p.copy())); - } -}; -function sumRolls(parts) { - const meaningful = parts.filter((p) => p !== void 0); - if (meaningful.length === 0) return new RollBuilder(0); - if (meaningful.length === 1) return meaningful[0]; - return new CompositeSumRollBuilder(meaningful); -} - -// src/builder/factory.ts -var rollFn = (count, sidesOrDie, modifier) => { - if (sidesOrDie instanceof RollBuilder) { - if (sidesOrDie.hasHiddenState()) { - throw new Error( - "Cannot use a roll with hidden state (like a pooled roll) as a die type." - ); - } - const subRollConfigs = sidesOrDie.getSubRollConfigs(); - if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier); - const absCount = Math.abs(count); - const newConfigs = subRollConfigs.map((config) => ({ - ...config, - count: config.count * absCount, - modifier: config.modifier * absCount - })); - let resultBuilder = new RollBuilder(newConfigs); - if (count < 0) { - const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction })); - resultBuilder = new RollBuilder(negatedConfigs); - } - return resultBuilder.plus(modifier); - } else { - let builder = new RollBuilder(count); - if (sidesOrDie && sidesOrDie > 0) { - builder = builder.d(sidesOrDie); - } - return builder.plus(modifier); - } -}; -rollFn.d = (sides) => { - if (typeof sides === "string") { - return RollBuilder.fromArgs(sides); - } - return new RollBuilder(1).d(sides); -}; -rollFn.hd20 = () => new RollBuilder(1).d20().reroll(1); -rollFn.d4 = () => new RollBuilder(1).d4(); -rollFn.d6 = () => new RollBuilder(1).d6(); -rollFn.d8 = () => new RollBuilder(1).d8(); -rollFn.d10 = () => new RollBuilder(1).d10(); -rollFn.d12 = () => new RollBuilder(1).d12(); -rollFn.d20 = () => new RollBuilder(1).d20(); -rollFn.d100 = () => new RollBuilder(1).d100(); -rollFn.flat = (n) => new RollBuilder(0).plus(n); -function d(sides) { - if (typeof sides === "string") { - return RollBuilder.fromArgs(sides); - } - return new RollBuilder(1).d(sides); -} -var d4 = new RollBuilder(1).d4(); -var d6 = new RollBuilder(1).d6(); -var d8 = new RollBuilder(1).d8(); -var d10 = new RollBuilder(1).d10(); -var d12 = new RollBuilder(1).d12(); -var d20 = new RollBuilder(1).d20(); -var hd20 = new RollBuilder(1).d20().reroll(1); -var d100 = new RollBuilder(1).d100(); -var flat = (n) => new RollBuilder(0).plus(n); -var roll = rollFn; -var builderPMFCache = new LRUCache(1e3); - -// src/builder/ast.ts -var defaultEps = 0; -var singleDiePMFCache = new LRUCache(1e3); -function astFromRollConfigs(configs) { - if (!configs || configs.length === 0) return void 0; - const children = []; - let constantSum = 0; - for (const cfg of configs) { - const sign = cfg.isSubtraction || cfg.count < 0 ? -1 : 1; - const count = Math.abs(cfg.count || 0); - constantSum += cfg.modifier || 0; - if ((cfg.sides || 0) <= 0) continue; - const die = { - type: "die", - sides: cfg.sides, - reroll: cfg.reroll > 0 ? cfg.reroll : void 0, - minimum: cfg.minimum > 0 ? cfg.minimum : void 0, - explode: cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0 ? cfg.explode : void 0 - }; - let node = die; - let appliedRollType = false; - if (cfg.rollType && cfg.rollType !== "flat") { - if (cfg.sides === 20) { - node = { - type: "d20Roll", - rollType: cfg.rollType, - child: node - }; - } else { - const n = cfg.rollType === "elven accuracy" ? 3 : 2; - const mode = cfg.rollType === "disadvantage" ? "lowest" : "highest"; - const base = { type: "sum", count: n, child: node }; - node = { type: "keep", mode, count: 1, child: base }; - } - appliedRollType = true; - } - if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) { - const baseCount = Math.max(1, Math.floor(Math.abs(count || 1))); - const trials = Math.max(1, Math.floor(cfg.keep.total)); - const k = Math.max(0, Math.floor(cfg.keep.count)); - if (k === 1 && cfg.keep.mode === "highest") { - const perTrial = { - type: "sum", - count: baseCount, - child: node - }; - if (trials === 1) { - node = perTrial; - } else { - node = { - type: "maxOf", - count: trials, - child: perTrial - }; - } - } else if (trials === baseCount) { - const base = { type: "sum", count: trials, child: node }; - node = { - type: "keep", - mode: cfg.keep.mode, - count: k, - child: base - }; - } else { - const perTrial = { - type: "sum", - count: baseCount, - child: node - }; - if (trials === 1) { - node = perTrial; - } else { - const trialPool = { - type: "sum", - count: trials, - child: perTrial - }; - node = { - type: "keep", - mode: cfg.keep.mode, - count: k, - child: trialPool - }; - } - } - } else { - const c = appliedRollType ? 1 : Math.max(1, count || 1); - node = { type: "sum", count: c, child: node }; - } - children.push({ node, sign }); - } - if (children.length === 0) { - return { type: "constant", value: constantSum }; - } - const add = { type: "add", children }; - if (constantSum !== 0) - add.children.push({ - node: { type: "constant", value: constantSum }, - sign: 1 - }); - return add; -} -function resolve(node, eps = defaultEps) { - const signature = getASTSignature(node); - const cacheKey = `${signature}_${eps}`; - const cached = builderPMFCache.get(cacheKey); - if (cached) return cached; - const result = (() => { - switch (node.type) { - case "constant": - return PMF.delta(node.value, eps); - case "die": { - return resolveSingleDie(node, eps); - } - case "sum": { - const base = resolve(node.child, eps); - const n = Math.max(0, Math.floor(node.count)); - if (n === 0) return PMF.delta(0, eps); - if (n === 1) return base; - return base.power(n, eps); - } - case "add": { - let shift = 0; - const parts = []; - for (const c of node.children) { - if (c.node.type === "constant") { - shift += c.sign * c.node.value; - } else { - const p = resolve(c.node, eps); - parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v)); - } - } - if (parts.length === 0) return PMF.delta(shift, eps); - let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps); - if (shift !== 0) res = res.mapDamage((v) => v + shift); - return res; - } - case "keep": { - const totalTrials = getTotalCount(node); - const keepCount = Math.max(0, Math.min(node.count, totalTrials)); - if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps); - const perTrialNode = node.child.child; - const perTrialPMF = resolve(perTrialNode, eps); - return keepSumPMF( - perTrialPMF, - totalTrials, - keepCount, - node.mode === "highest", - eps - ); - } - case "d20Roll": { - const childDie = findDie(node.child); - const rerollOne = !!childDie && (childDie.reroll || 0) >= 1; - return d20RollPMF(node.rollType, rerollOne); - } - case "half": { - const childPMF = resolve(node.child, eps); - return childPMF.scaleDamage(0.5, "floor"); - } - case "maxOf": { - const childPMF = resolve(node.child, eps); - const count = Math.max(1, Math.floor(node.count)); - if (count === 1) return childPMF; - return computeMaxOfPMF(childPMF, count, eps); - } - case "scale": { - const childPMF = resolve(node.child, eps); - const denom = node.denominator === 0 ? 1 : node.denominator; - return childPMF.scaleDamage(node.numerator / denom, node.rounding); - } - } - })(); - builderPMFCache.set(cacheKey, result); - return result; -} -function pmfFromRollBuilder(rb, eps = defaultEps) { - const ast = rb.toAST(); - return resolve(ast, eps); -} -function resolveSingleDie(die, eps = defaultEps) { - const signature = getASTSignature(die); - const cacheKey = `${signature}_${eps}`; - const cached = singleDiePMFCache.get(cacheKey); - if (cached) return cached; - const s = Math.max(0, Math.floor(die.sides)); - if (s <= 0) return PMF.delta(0, eps); - let probs = /* @__PURE__ */ new Map(); - for (let v = 1; v <= s; v++) probs.set(v, 1 / s); - const r = Math.max(0, Math.floor(die.reroll || 0)); - if (r > 0) { - const k = Math.min(r, s); - const rerollMass = k / s; - const uniformReroll = rerollMass / s; - const next = /* @__PURE__ */ new Map(); - for (let v = 1; v <= s; v++) { - const keep = v <= k ? 0 : 1 / s; - next.set(v, keep + uniformReroll); - } - probs = next; - } - let pmf = PMF.fromMap(new Map(probs), eps); - const minV = Math.max(0, Math.floor(die.minimum || 0)); - if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV)); - const explode = die.explode; - if (explode && Number.isFinite(explode) && explode > 0) { - const times = Math.floor(explode); - const maxFace = s; - const nonMax = /* @__PURE__ */ new Map(); - const pMax = pmf.pAt(maxFace); - for (const v of pmf.support()) { - if (v !== maxFace) nonMax.set(v, pmf.pAt(v)); - } - let nonMaxPMF = PMF.fromMap(nonMax, eps); - if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) { - nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax); - } - let tail = PMF.delta(0, eps); - const addOnce = pmf; - for (let t = 1; t <= times; t++) { - tail = tail.convolve(addOnce, eps); - } - const exploded = PMF.branch( - tail.mapDamage((v) => v + maxFace), - nonMaxPMF, - pMax - ); - pmf = exploded; - } - singleDiePMFCache.set(cacheKey, pmf); - return pmf; -} -function findDie(node) { - switch (node.type) { - case "die": - return node; - case "constant": - return void 0; - case "sum": - case "d20Roll": - case "half": - case "maxOf": - case "scale": - return findDie(node.child); - case "keep": - return findDie(node.child.child); - case "add": - for (const c of node.children) { - const d2 = findDie(c.node); - if (d2) return d2; - } - return void 0; - } -} -function getTotalCount(node) { - let cur = node.child; - while (cur.type === "keep") cur = cur.child; - return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0; -} -function computeMaxOfPMF(pmf, count, eps = defaultEps) { - if (count <= 1) return pmf; - const support = pmf.support(); - const out = /* @__PURE__ */ new Map(); - if (count <= 6 && support.length <= 20) { - let dfs2 = function(rollsLeft, currentMax, probability) { - if (rollsLeft === 0) { - out.set(currentMax, (out.get(currentMax) || 0) + probability); - return; - } - for (const value of support) { - const p = pmf.pAt(value); - if (p > 0) { - const newMax = Math.max(currentMax, value); - dfs2(rollsLeft - 1, newMax, probability * p); - } - } - }; - dfs2(count, -Infinity, 1); - } else { - const sortedSupport = [...support].sort((a, b) => a - b); - let runningCdf = 0; - for (const value of sortedSupport) { - const prevCdf = runningCdf; - runningCdf += pmf.pAt(value); - const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count); - if (probMax > eps) { - out.set(value, probMax); - } - } - } - return PMF.fromMap(out, eps); -} -function keepSumPMF(single, total, keep, highest, eps = defaultEps) { - if (keep >= total) return single.power(total, eps); - if (keep <= 0) return PMF.delta(0, eps); - const sortedSupport = [...single.support()].sort((a, b) => a - b); - const pmfSig = sortedSupport.map((val) => `${val}:${single.pAt(val).toPrecision(6)}`).join(","); - const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`; - const cached = builderPMFCache.get(cacheKey); - if (cached) return cached; - if (keep === 1) { - if (highest) { - return computeMaxOfPMF(single, total, eps); - } else { - const neg = single.mapDamage((v) => -v); - const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v); - builderPMFCache.set(cacheKey, minPMF); - return minPMF; - } - } - let state = /* @__PURE__ */ new Map(); - const stride = total + 1; - const keyOf = (used, r) => used * stride + r; - state.set(keyOf(0, total), /* @__PURE__ */ new Map([[0, 1]])); - const valuesDesc = highest ? [...sortedSupport].sort((a, b) => b - a) : [...sortedSupport].sort((a, b) => a - b); - const binomPMF = (r, p) => { - if (r <= 0) return [1]; - if (p <= eps) { - const arr2 = new Array(r + 1).fill(0); - arr2[0] = 1; - return arr2; - } - if (1 - p <= eps) { - const arr2 = new Array(r + 1).fill(0); - arr2[r] = 1; - return arr2; - } - const q = 1 - p; - const arr = new Array(r + 1).fill(0); - arr[0] = Math.pow(q, r); - const ratio = p / q; - for (let x = 1; x <= r; x++) - arr[x] = arr[x - 1] * (r - x + 1) / x * ratio; - let s = 0; - for (let x = 0; x <= r; x++) s += arr[x]; - if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s; - return arr; - }; - const pruneMap = (m, threshold) => { - if (threshold <= 0) return m; - const out = /* @__PURE__ */ new Map(); - for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr); - return out.size === m.size ? m : out; - }; - const pruneState = (st, threshold) => { - if (threshold <= 0) return st; - const out = /* @__PURE__ */ new Map(); - for (const [k, m] of st) { - const mm = pruneMap(m, threshold); - if (mm.size > 0) out.set(k, mm); - } - return out; - }; - let processedMass = 0; - for (const v of valuesDesc) { - const p = single.pAt(v); - if (p <= 0) continue; - const q = Math.max(eps, 1 - processedMass); - const pCond = Math.min(1, p / q); - const next = /* @__PURE__ */ new Map(); - for (const [k, m] of state) { - const used = Math.floor(k / stride); - const r = k - used * stride; - if (r === 0) { - const destKey = keyOf(used, 0); - const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); - for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr); - next.set(destKey, dest); - continue; - } - const bin = binomPMF(r, pCond); - const remainingCapacity = keep - used; - for (let x = 0; x <= r; x++) { - const px = bin[x]; - if (px <= eps) continue; - const t = Math.min(x, remainingCapacity); - const used2 = used + t; - const r2 = r - x; - const add = t * v; - const destKey = keyOf(used2, r2); - const dest = next.get(destKey) ?? /* @__PURE__ */ new Map(); - for (const [sum, pr] of m) { - const s2 = sum + add; - const prob = pr * px; - const cur = dest.get(s2) || 0; - const nv = cur + prob; - if (nv >= eps) dest.set(s2, nv); - } - if (dest.size > 0) next.set(destKey, dest); - } - } - state = pruneState(next, eps * 1e-6); - processedMass += p; - } - const finalKey = keyOf(keep, 0); - const dist = state.get(finalKey) ?? /* @__PURE__ */ new Map(); - if (dist.size === 0) { - return PMF.emptyMass(); - } - const result = PMF.fromMap(dist, eps); - builderPMFCache.set(cacheKey, result); - return result; -} -function getASTSignature(node) { - switch (node.type) { - case "constant": - return `c:${node.value}`; - case "die": { - const parts = []; - parts.push(`s:${node.sides}`); - if (node.reroll) parts.push(`r:${node.reroll}`); - if (node.minimum) parts.push(`m:${node.minimum}`); - if (node.explode) parts.push(`e:${node.explode}`); - return `d{${parts.join(",")}}`; - } - case "sum": - return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`; - case "d20Roll": - return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`; - case "keep": - return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature( - node.child - )}}`; - case "half": - return `half{ch:${getASTSignature(node.child)}}`; - case "maxOf": - return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`; - case "scale": - return `scale{n:${node.numerator},d:${node.denominator},r:${node.rounding},ch:${getASTSignature(node.child)}}`; - case "add": { - let constantValue = 0; - const otherChildrenSigs = []; - for (const c of node.children) { - if (c.node.type === "constant") { - constantValue += c.sign * c.node.value; - } else { - otherChildrenSigs.push( - `${c.sign === -1 ? "-" : "+"}${getASTSignature(c.node)}` - ); - } - } - if (constantValue !== 0) { - otherChildrenSigs.push( - constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}` - ); - } - otherChildrenSigs.sort(); - return `add[${otherChildrenSigs.join("")}]`; - } - } -} - -// src/builder/attack.ts -var AttackBuilder = class _AttackBuilder { - constructor(check, hitEffect, critEffect, missEffect) { - this.check = check; - this.hitEffect = hitEffect; - this.critEffect = critEffect; - this.missEffect = missEffect; - } - onCrit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new _AttackBuilder( - this.check, - this.hitEffect, - damageRoll, - this.missEffect - ); - } - onMiss(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new _AttackBuilder( - this.check, - this.hitEffect, - this.critEffect, - damageRoll - ); - } - noCrit() { - return new _AttackBuilder(this.check, this.hitEffect, null, this.missEffect); - } - // Legacy expressions - toExpression() { - const checkPart = this.check.toExpression(); - let effectPart = ""; - if (this.hitEffect) { - effectPart = `(${this.hitEffect.toExpression()})`; - if (this.critEffect !== null) { - let crit; - if (this.critEffect) { - crit = this.critEffect; - } else { - if (this.hitEffect instanceof ParsedRollBuilder) { - crit = RollBuilder.fromArgs(0); - } else { - crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0); - } - } - const critThreshold = this.check.critThreshold; - if (critThreshold < 1 || critThreshold > 20) { - throw new Error( - `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.` - ); - } - const critExpression = crit.toExpression(); - if (critExpression !== "0") { - if (critThreshold === 20) { - effectPart += ` crit (${critExpression})`; - } else { - const xcritNumber = 21 - critThreshold; - effectPart += ` xcrit${xcritNumber} (${critExpression})`; - } - } - } - if (this.missEffect) { - effectPart += ` miss (${this.missEffect.toExpression()})`; - } - } - return `${checkPart} * ${effectPart}`; - } - resolveProbabilities(check, eps = 0) { - const rollType = check.rollType; - const rerollOne = check.baseReroll > 0; - const critThreshold = check.critThreshold; - const d202 = d20RollPMF(rollType, rerollOne); - if (check instanceof AlwaysCritBuilder) { - if (check.fromAlwaysHit) { - return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 }; - } - const ac2 = check.attackConfig.ac ?? 0; - const staticMod2 = this.check.modifier; - const bonusDicePMFs2 = this.check.getBonusDicePMFs(this.check, eps); - const bonusPMF2 = bonusDicePMFs2.length ? PMF.convolveMany(bonusDicePMFs2, eps) : PMF.delta(0, eps); - let pcrit2 = 0; - let pmiss2 = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - if (r === 1) { - pmiss2 += pr; - continue; - } - const need = ac2 - staticMod2 - r; - const pBonusHit = bonusPMF2.tailProbGE(need); - pcrit2 += pr * pBonusHit; - pmiss2 += pr * (1 - pBonusHit); - } - return { pSuccess: pcrit2, pHit: 0, pCrit: pcrit2, pMiss: pmiss2 }; - } - if (check instanceof AlwaysHitBuilder) { - let pCrit = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - if (r >= critThreshold) pCrit += pr; - } - const pHit = 1 - pCrit; - const pMiss = 0; - return { pSuccess: 1, pHit, pCrit, pMiss }; - } - const ac = check.attackConfig.ac; - const staticMod = this.check.modifier; - const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps); - const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); - let pcrit = 0; - let phit = 0; - let pmiss = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - if (r === 1) { - pmiss += pr; - continue; - } - if (r >= critThreshold) { - pcrit += pr; - continue; - } - const need = ac - staticMod - r; - const pBonusHit = bonusPMF.tailProbGE(need); - phit += pr * pBonusHit; - pmiss += pr * (1 - pBonusHit); - } - const psuccess = phit + pcrit; - return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss }; - } - resolve(eps = EPS) { - const { - pHit, - pCrit, - pMiss: pmiss - } = this.resolveProbabilities(this.check, eps); - const hitPMF = this.hitEffect ? this.hitEffect instanceof ParsedRollBuilder ? this.hitEffect.toPMF(eps) : pmfFromRollBuilder(this.hitEffect, eps) : PMF.delta(0, eps); - let critPMF = null; - let phit = pHit; - let pcrit = pCrit; - if (this.critEffect === null) { - critPMF = null; - phit += pcrit; - pcrit = 0; - } else { - let critBuilder; - if (this.critEffect) { - critBuilder = this.critEffect; - } else if (this.hitEffect instanceof ParsedRollBuilder) { - critPMF = null; - phit += pcrit; - pcrit = 0; - critBuilder = void 0; - } else { - critBuilder = this.hitEffect?.copy().doubleDice(); - } - if (critBuilder) { - critPMF = critBuilder instanceof ParsedRollBuilder ? critBuilder.toPMF(eps) : pmfFromRollBuilder(critBuilder, eps); - } - } - const missPMF = this.missEffect ? this.missEffect instanceof ParsedRollBuilder ? this.missEffect.toPMF(eps) : pmfFromRollBuilder(this.missEffect, eps) : PMF.delta(0, eps); - const mix = new Mixture(eps); - if (phit > 0) mix.add("hit", hitPMF, phit); - if (critPMF && pcrit > 0) mix.add("crit", critPMF, pcrit); - if (pmiss > 0) - mix.add(this.missEffect ? "missDamage" : "missNone", missPMF, pmiss); - return { - pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps), - check: this.check.toPMF(eps) ?? PMF.delta(0, eps), - hit: hitPMF ?? PMF.delta(0, eps), - crit: critPMF ?? PMF.delta(0, eps), - miss: missPMF ?? PMF.delta(0, eps), - weights: { hit: phit, crit: pcrit, miss: pmiss } - }; - } - // By default, create PMF with no pruning - toPMF(eps = 0) { - return this.resolve(eps).pmf; - } - get pmf() { - return this.toPMF(); - } - // By default, create query on PMF with no pruning - toQuery(eps = 0) { - return this.toPMF(eps).query(); - } -}; - -// src/builder/ac.ts -var ACBuilder = class _ACBuilder extends RollBuilder { - constructor(baseRoll, ac, attackConfig) { - super(baseRoll.getSubRollConfigs()); - if (attackConfig) { - this.attackConfig = { ...attackConfig, ac }; - } else { - this.attackConfig = { ac, critThreshold: 20 }; - } - } - onHit(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new AttackBuilder(this, damageRoll); - } - get critThreshold() { - return this.attackConfig.critThreshold; - } - // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error? - critOn(threshold) { - const newConfig = { - ...this.attackConfig, - critThreshold: threshold - }; - return new _ACBuilder(this, this.attackConfig.ac, newConfig); - } - alwaysCrits() { - return new AlwaysCritBuilder( - this, - { - critThreshold: this.attackConfig.critThreshold, - ac: this.attackConfig.ac - }, - false - ); - } - // Legacy expressions - toExpression() { - const configs = this.getSubRollConfigs(); - const expression = new RollBuilder(configs).toExpression(); - return this.attackConfig.ac ? `(${expression} AC ${this.attackConfig.ac})` : expression; - } - toPMF(eps = 0) { - const ac = this.attackConfig.ac; - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - const d202 = d20RollPMF(rollType, rerollOne); - const staticMod = this.modifier; - const bonusPMFs = this.getBonusDicePMFs(this, eps); - const parts = [d202, ...bonusPMFs]; - let attackRollPMF = parts.length === 1 ? d202 : PMF.convolveMany(parts, eps); - if (staticMod !== 0) - attackRollPMF = attackRollPMF.mapDamage( - (rollValue) => rollValue + staticMod - ); - const out = /* @__PURE__ */ new Map(); - for (const rollValue of attackRollPMF.support()) { - const p = attackRollPMF.pAt(rollValue); - const key = rollValue >= ac ? rollValue : 0; - out.set(key, (out.get(key) || 0) + p); - } - return PMF.fromMap(out, eps); - } - copy() { - const baseCopy = new RollBuilder(this.getSubRollConfigs()); - const newConfig = { - ac: this.attackConfig.ac, - critThreshold: this.attackConfig.critThreshold - }; - return new _ACBuilder(baseCopy, newConfig.ac, newConfig); - } -}; -RollBuilder.prototype.ac = function(targetAC) { - if (isNaN(targetAC)) throw new Error("Invalid NaN value for targetAC"); - return new ACBuilder(this, targetAC); -}; - -// src/builder/save.ts -var SaveBuilder = class _SaveBuilder { - constructor(check, failureEffect, saveOutcome = "normal") { - this.check = check; - this.failureEffect = failureEffect; - this.saveOutcome = saveOutcome; - } - saveHalf() { - return new _SaveBuilder(this.check, this.failureEffect, "half"); - } - toExpression() { - const checkPart = this.check.toExpression(); - if (!this.failureEffect) return checkPart; - const failureEffectPart = this.failureEffect.toExpression(); - const result = `${checkPart} * (${failureEffectPart})`; - return this.saveOutcome === "half" ? `${result} save half` : result; - } - resolve(eps = EPS) { - const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities( - this.check - ); - const failPMF = this.failureEffect ? this.failureEffect instanceof ParsedRollBuilder ? this.failureEffect.toPMF(eps) : pmfFromRollBuilder(this.failureEffect) : PMF.delta(0); - const onSuccess = this.saveOutcome ?? "half"; - let successPMF = PMF.delta(0, eps); - if (onSuccess === "half") successPMF = failPMF.scaleDamage(0.5, "floor"); - const successLabel = onSuccess === "normal" ? "missNone" : "saveHalf"; - const failLabel = "saveFail"; - const baseMix = new Mixture(eps); - const mixture = baseMix.add(successLabel, successPMF, psuccess).add(failLabel, failPMF, pfail); - return { - pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps), - check: PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps), - saveFail: failPMF ?? PMF.delta(0, eps), - saveSuccess: successPMF ?? PMF.delta(0, eps), - weights: { success: psuccess, fail: pfail } - }; - } - // By default, create PMF with no pruning - toPMF(eps = 0) { - return this.resolve(eps).pmf; - } - get pmf() { - return this.toPMF(); - } - // By default, create query on PMF with no pruning - toQuery(eps = 0) { - return this.toPMF(eps).query(); - } -}; -function resolveProbabilities(check) { - const saveBonus = check.modifier; - const dc = check.saveDC; - const d20Type = check.rollType; - const baseReroll = check.baseReroll; - const die = d20RollPMF(d20Type, baseReroll > 0); - const faceP = /* @__PURE__ */ new Map(); - for (const [r, bin] of die) { - const pr = bin.p; - if (pr > 0) faceP.set(r, pr); - } - const eps = 0; - const bonusDicePMFs = check.getBonusDicePMFs(check, eps); - const bonusPMF = bonusDicePMFs.length > 0 ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.zero(eps); - let pSuccess = 0; - for (let r = 1; r <= 20; r++) { - const pr = faceP.get(r); - if (!pr) continue; - const need = dc - saveBonus - r; - pSuccess += pr * bonusPMF.tailProbGE(need); - } - const pFail = Math.max(0, 1 - pSuccess); - return { pSuccess, pFail }; -} - -// src/builder/dc.ts -var DCBuilder = class _DCBuilder extends RollBuilder { - constructor(baseRoll, saveConfig) { - super(baseRoll.getSubRollConfigs()); - this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 }; - } - dc(saveDC) { - if (this.rollType && this.rollType === "elven accuracy") { - throw new Error( - "Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead." - ); - } - return new _DCBuilder(this, { dc: saveDC }); - } - get saveDC() { - return this.saveConfig.dc; - } - add(anotherRoll) { - const newBuilder = super.add(anotherRoll); - return new _DCBuilder(newBuilder, this.saveConfig); - } - addRoll(count) { - const newBuilder = super.addRoll(count); - return new _DCBuilder(newBuilder, this.saveConfig); - } - onSaveFailure(...args) { - const damageRoll = RollBuilder.fromArgs(...args); - return new SaveBuilder(this, damageRoll); - } - withElvenAccuracy() { - throw new Error( - "Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks)." - ); - } - // Legacy expressions - toExpression() { - const subConfigs = this.getSubRollConfigs(); - const allConfigs = [...subConfigs]; - const expression = new RollBuilder(allConfigs).toExpression(); - return `(${expression} DC ${this.saveConfig.dc})`; - } - toPMF(eps = 0) { - const saveDC = this.saveDC; - const rollType = this.rollType; - const rerollOne = this.baseReroll > 0; - const d202 = d20RollPMF(rollType, rerollOne); - const staticMod = this.modifier; - const bonusDicePMFs = this.getBonusDiceConfigs().map( - (cfg) => pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps) - ); - const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps); - let psuccess = 0; - for (const [r, bin] of d202) { - const pr = bin.p; - if (pr <= 0) continue; - const need = saveDC - staticMod - r; - psuccess += pr * bonusPMF.tailProbGE(need); - } - const pfail = Math.max(0, 1 - psuccess); - const m = /* @__PURE__ */ new Map([ - [0, psuccess > 0 ? psuccess : 0], - [1, pfail > 0 ? pfail : 0] - ]); - return PMF.fromMap(m, eps); - } -}; -RollBuilder.prototype.dc = function(saveDC) { - if (isNaN(saveDC)) throw new Error("Invalid NaN value for saveDC"); - return new DCBuilder(this).dc(saveDC); -}; - -export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, DCBuilder, HalfRollBuilder, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, RollBuilder, SaveBuilder, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls }; -//# sourceMappingURL=index.js.map -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/builder/index.js.map b/dist/builder/index.js.map deleted file mode 100644 index 7d9ec09..0000000 --- a/dist/builder/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/common/lru-cache.ts","../../src/common/types.ts","../../src/pmf/query.ts","../../src/pmf/pmf.ts","../../src/pmf/mixture.ts","../../src/common/errors.ts","../../src/parser/dice.ts","../../src/parser/parser.ts","../../src/builder/prob.ts","../../src/builder/d20.ts","../../src/builder/roll.ts","../../src/builder/factory.ts","../../src/builder/ast.ts","../../src/builder/attack.ts","../../src/builder/ac.ts","../../src/builder/save.ts","../../src/builder/dc.ts"],"names":["d","cumulativeSum","result","dfs","arr","d20","ac","staticMod","bonusDicePMFs","bonusPMF","pcrit","pmiss"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import { EPS, PMF } from \"../\";\n\nexport function d20PmfFromCdf(\n cdfPow: (k: number) => number,\n eps: number = EPS\n): PMF {\n const out = new Map();\n let prev = 0;\n for (let k = 1; k <= 20; k++) {\n const cur = cdfPow(k);\n const pk = cur - prev;\n if (pk > 0) {\n out.set(k, pk);\n }\n prev = cur;\n }\n\n return PMF.fromMap(out, eps);\n}\n","import { EPS, PMF, pmfCache } from \"../\";\nimport { d20PmfFromCdf } from \"./prob\";\nimport type { RollType } from \"./types\";\n\nconst cacheKeyMap: Record = {\n \"flat-flat\": \"d20\",\n \"flat-reroll\": \"hd20\",\n \"advantage-flat\": \"d20 > d20\",\n \"advantage-reroll\": \"hd20 > hd20\",\n \"disadvantage-flat\": \"d20 < d20\",\n \"disadvantage-reroll\": \"hd20 < hd20\",\n \"elven accuracy-flat\": \"d20 > d20 > d20\",\n \"elven accuracy-reroll\": \"hd20 > hd20 > hd20\",\n};\n\n/** Lift a single d20 PMF into advantage, disadvantage, or elven (triple-advantage). */\nexport function d20RollPMF(\n rollType: RollType | undefined,\n rerollOne: boolean = false\n): PMF {\n rollType = rollType || \"flat\";\n const cacheKeyLookup = `${rollType}-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cacheKey = cacheKeyMap[cacheKeyLookup];\n if (!cacheKey) {\n throw new Error(`Invalid roll type: ${rollType}`);\n }\n\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const base = d20PMF(rerollOne);\n if (!rollType || rollType === \"flat\") {\n pmfCache.set(cacheKey, base);\n return base;\n }\n\n const p: number[] = new Array(21).fill(0); // indices 1..20\n for (const [r, rec] of base) {\n const pr = typeof rec === \"number\" ? rec : rec.p;\n if (r >= 1 && r <= 20) p[r] = pr;\n }\n const F: number[] = new Array(21).fill(0);\n for (let k = 1; k <= 20; k++) F[k] = F[k - 1] + p[k];\n\n const eps = 0;\n let result = base;\n if (rollType === \"advantage\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 2), eps);\n } else if (rollType === \"elven accuracy\") {\n result = d20PmfFromCdf((k) => Math.pow(F[k], 3), eps);\n } else if (rollType === \"disadvantage\") {\n result = d20PmfFromCdf((k) => 1 - Math.pow(1 - F[k], 2), eps);\n }\n\n pmfCache.set(cacheKey, result);\n return result;\n}\n\nexport function d20PMF(rerollOne: boolean): PMF {\n const cacheKey = `flat-${rerollOne ? \"reroll\" : \"flat\"}`;\n const cached = pmfCache.get(cacheKey);\n if (cached) return cached;\n\n const m = new Map();\n const base = 1 / 20;\n const rerollShare = base * base;\n if (!rerollOne) {\n for (let r = 1; r <= 20; r++) {\n m.set(r, base);\n }\n } else {\n for (let r = 1; r <= 20; r++) {\n m.set(r, (r === 1 ? 0 : base) + rerollShare);\n }\n }\n const result = PMF.fromMap(m, EPS);\n pmfCache.set(cacheKey, result);\n return result;\n}\n","import type { ACBuilder } from \"./ac\";\nimport type { CritConfig } from \"../common/types\";\nimport type { DCBuilder } from \"./dc\";\nimport { parse } from \"../parser/parser\";\nimport type { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { astFromRollConfigs, pmfFromRollBuilder } from \"./ast\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport type { ExpressionNode, KeepNode, SumNode } from \"./nodes\";\nimport type { RollConfig, RollType } from \"./types\";\n\nexport const defaultConfig: RollConfig = {\n count: 1,\n sides: 0,\n modifier: 0,\n reroll: 0,\n explode: 0,\n minimum: 0,\n bestOf: 0,\n keep: undefined,\n rollType: \"flat\",\n};\n\nconst rollConfigsEqual = (a: RollConfig, b: RollConfig) => {\n return (\n a.count === b.count &&\n a.sides === b.sides &&\n a.modifier === b.modifier &&\n a.reroll === b.reroll &&\n a.explode === b.explode &&\n a.minimum === b.minimum &&\n a.bestOf === b.bestOf &&\n a.keep === b.keep &&\n a.rollType === b.rollType\n );\n};\n\nconst configComplexityScore = (config: RollConfig) => {\n return (\n (config.reroll > 0 ? 1 : 0) +\n (config.explode > 0 ? 1 : 0) +\n (config.minimum > 0 ? 1 : 0) +\n (config.bestOf > 0 ? 1 : 0) +\n (config.keep !== undefined ? 1 : 0) +\n (config.rollType !== \"flat\" ? 1 : 0)\n );\n};\n\n// Fluent builder for dice to create PMFs with an AST\nexport class RollBuilder {\n protected readonly subRollConfigs: readonly RollConfig[];\n\n constructor(countOrConfigs: number | readonly RollConfig[] = 1) {\n if (typeof countOrConfigs === \"number\") {\n const count = countOrConfigs;\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n this.subRollConfigs = [\n { ...defaultConfig, count, isSubtraction: count < 0 },\n ];\n } else {\n this.subRollConfigs = countOrConfigs.map((c) => ({ ...c }));\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n protected get lastConfig() {\n return this.subRollConfigs[this.subRollConfigs.length - 1];\n }\n\n hasHiddenState(): boolean {\n return false;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.subRollConfigs.map((c: RollConfig) => ({ ...c }));\n }\n\n // for testing\n static fromConfig(config: Partial): RollBuilder {\n return new RollBuilder([{ ...defaultConfig, ...config }]);\n }\n\n static fromConfigs(configs: Partial[]): RollBuilder {\n return new RollBuilder(\n configs.map((config) => ({ ...defaultConfig, ...config }))\n );\n }\n\n static fromArgs(...args: any[]): RollBuilder {\n if (args.length === 1) {\n const arg = args[0];\n if (typeof arg === \"number\") {\n if (isNaN(arg)) throw new Error(\"Invalid NaN value for argument\");\n return new RollBuilder(0).plus(arg);\n }\n if (typeof arg === \"string\") {\n return new ParsedRollBuilder(arg);\n }\n if (arg instanceof RollBuilder) {\n return arg;\n }\n }\n\n if (args.length === 2 || args.length === 3) {\n const [count, sidesOrDie, modifier] = args;\n\n if (typeof count !== \"number\") {\n throw new Error(\"First argument must be a number for multi-arg call\");\n }\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count argument\");\n\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) {\n const result = new RollBuilder(0);\n return modifier !== undefined ? result.plus(modifier) : result;\n }\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return modifier !== undefined\n ? resultBuilder.plus(modifier)\n : resultBuilder;\n } else if (typeof sidesOrDie === \"number\" || sidesOrDie === undefined) {\n if (typeof sidesOrDie === \"number\" && isNaN(sidesOrDie))\n throw new Error(\"Invalid NaN value for sides argument\");\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return modifier !== undefined ? builder.plus(modifier) : builder;\n }\n }\n\n throw new Error(`Invalid arguments passed: ${args.join(\", \")}`);\n }\n\n // --- Core Dice Methods ---\n d(sides: number | undefined): RollBuilder {\n if (sides !== undefined && isNaN(sides))\n throw new Error(\"Invalid NaN value for sides\");\n if (sides === undefined) return this;\n if (this.lastConfig.sides && this.lastConfig.sides > 0) {\n throw new Error(\"Cannot add a die after adding a die\");\n }\n if (sides === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].sides = sides;\n return this.create(newConfigs);\n }\n\n plus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n plus(count: number, die: RollBuilder): RollBuilder;\n plus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n if (typeof modOrRoll === \"number\" && isNaN(modOrRoll))\n throw new Error(\"Invalid NaN value for modOrRoll\");\n if (die instanceof RollBuilder && typeof modOrRoll === \"number\") {\n if (die.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n const count = modOrRoll;\n const subRollConfigs = die.getSubRollConfigs();\n if (subRollConfigs.length === 0) return this;\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let rollToAdd = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = rollToAdd\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n rollToAdd = new RollBuilder(negatedConfigs);\n }\n return this.add(rollToAdd);\n }\n\n if (die !== undefined) {\n throw new Error(\"Invalid arguments to plus()\");\n }\n\n if (modOrRoll === undefined) return this;\n if (typeof modOrRoll === \"number\") {\n if (modOrRoll === 0) return this;\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].modifier += modOrRoll;\n return this.create(newConfigs);\n }\n return this.add(modOrRoll as RollBuilder);\n }\n\n minus(modOrRoll: number | RollBuilder | undefined): RollBuilder;\n minus(count: number, die: RollBuilder): RollBuilder;\n minus(\n modOrRoll: number | RollBuilder | undefined,\n die?: RollBuilder\n ): RollBuilder {\n const isNumber = typeof modOrRoll === \"number\";\n const dieIsRoll = die instanceof RollBuilder;\n if (dieIsRoll && isNumber) return this.plus(-modOrRoll, die);\n\n if (die !== undefined) throw new Error(\"Invalid arguments to minus()\");\n if (modOrRoll === undefined) return this;\n\n return isNumber\n ? this.plus(-modOrRoll)\n : this.plus(-1, modOrRoll as RollBuilder);\n }\n\n /** Apply one-pass reroll threshold (k): reroll faces 1..k once, must keep. */\n reroll(value: number): RollBuilder {\n if (isNaN(value)) throw new Error(\"Invalid NaN value for reroll\");\n if (value === this.lastConfig.reroll) return this;\n\n const newConfigs = this.getSubRollConfigs();\n\n newConfigs[newConfigs.length - 1].reroll = value;\n return this.create(newConfigs);\n }\n\n /** Set finite explode count for max-face explosions (Infinity allowed). */\n explode(count: number | undefined = Infinity): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for explode count\");\n if (count === undefined) return this;\n if (count === 0) return this;\n if (count < 0) throw new Error(\"Explode count must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].explode = count;\n return this.create(newConfigs);\n }\n\n /** Apply per-die minimum value (min > 0). */\n minimum(val: number | undefined): RollBuilder {\n if (val !== undefined && isNaN(val))\n throw new Error(\"Invalid NaN value for minimum\");\n if (val === undefined) return this;\n if (val === 0) return this;\n if (val < 0) throw new Error(\"Minimum value must be >= 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].minimum = val + 1;\n return this.create(newConfigs);\n }\n\n bestOf(count: number | undefined): RollBuilder {\n if (count !== undefined && isNaN(count))\n throw new Error(\"Invalid NaN value for bestOf count\");\n if (count === undefined) return this;\n if (count <= 0) throw new Error(\"Best of count must be > 0\");\n\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].bestOf = count;\n return this.create(newConfigs);\n }\n\n keepHighest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"highest\" };\n return this.create(newConfigs);\n }\n\n keepLowest(total: number, count: number): RollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowest\");\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].keep = { total, count, mode: \"lowest\" };\n return this.create(newConfigs);\n }\n\n keepHighestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepHighestAll\");\n const currentAST = this.toAST();\n // Wrap in SumNode to represent trials, then KeepNode\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"highest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kh${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n keepLowestAll(total: number, count: number): PooledRollBuilder {\n if (isNaN(total) || isNaN(count))\n throw new Error(\"Invalid NaN value for keepLowestAll\");\n const currentAST = this.toAST();\n const trialPool: SumNode = {\n type: \"sum\",\n count: total,\n child: currentAST,\n };\n const keepNode: KeepNode = {\n type: \"keep\",\n mode: \"lowest\",\n count,\n child: trialPool,\n };\n const currentExpr = this.toExpression();\n const expression = `${total}kl${count}(${currentExpr})`;\n return new PooledRollBuilder(keepNode, expression);\n }\n\n withAdvantage(): RollBuilder {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"advantage\";\n return this.create(newConfigs);\n }\n\n withDisadvantage(): RollBuilder {\n const configs = this.getSubRollConfigs();\n configs[configs.length - 1].rollType = \"disadvantage\";\n return this.create(configs);\n }\n\n add(anotherRoll: RollBuilder | undefined): RollBuilder {\n if (anotherRoll === undefined) return this;\n if (anotherRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot add a roll with hidden state (like a pooled roll) to a standard roll. Try adding the standard roll to the pooled roll instead: pool.plus(roll).\"\n );\n }\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n withBonus(anotherRoll: RollBuilder): RollBuilder {\n const configs = [...this.subRollConfigs, ...anotherRoll.subRollConfigs];\n return this.create(configs);\n }\n\n addRoll(count: number = 1): RollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for count\");\n const configs = [\n ...this.subRollConfigs,\n {\n ...defaultConfig,\n count,\n isSubtraction: count < 0,\n },\n ];\n return this.create(configs);\n }\n\n scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n const newConfigs = this.getSubRollConfigs().map((config) => {\n if (!config.sides || config.sides <= 0) return config;\n return { ...config, count: config.count * scaleInt };\n });\n return this.create(newConfigs);\n }\n\n doubleDice(): RollBuilder {\n return this.scaleDice(2);\n }\n\n alwaysHits() {\n return new AlwaysHitBuilder(this);\n }\n\n alwaysCrits() {\n return new AlwaysCritBuilder(this);\n }\n\n copy(): RollBuilder {\n return this.create(this.getSubRollConfigs());\n }\n\n // --- Dice Shortcut Methods ---\n d4 = () => this.d(4);\n d6 = () => this.d(6);\n d8 = () => this.d(8);\n d10 = () => this.d(10);\n d12 = () => this.d(12);\n d20 = () => this.d(20);\n d100 = () => this.d(100);\n\n withElvenAccuracy() {\n const newConfigs = this.getSubRollConfigs();\n newConfigs[newConfigs.length - 1].rollType = \"elven accuracy\";\n return this.create(newConfigs);\n }\n\n toExpression(): string {\n const originalDiceConfigs = this.subRollConfigs.filter(\n (config) => config.sides && config.sides > 0\n );\n\n type Group = { config: RollConfig; totalCount: number };\n const configGroups = new Map();\n\n for (const config of originalDiceConfigs) {\n const keyConfig: Partial = { ...config };\n delete keyConfig.count;\n delete keyConfig.modifier;\n const key = JSON.stringify(keyConfig);\n\n const existingGroup = configGroups.get(key);\n if (existingGroup) {\n existingGroup.totalCount += config.count;\n } else {\n configGroups.set(key, { config, totalCount: config.count });\n }\n }\n\n const rootConfig = this.getRootDieConfig();\n const groupedConfigs = Array.from(configGroups.values());\n let rootD20Group: Group | undefined;\n\n if (rootConfig && rootConfig.sides === 20) {\n const rootIndex = groupedConfigs.findIndex(\n ({ config }) =>\n rollConfigsEqual(config, rootConfig) &&\n JSON.stringify(config.keep) === JSON.stringify(rootConfig.keep)\n );\n\n if (rootIndex !== -1) {\n rootD20Group = groupedConfigs.splice(rootIndex, 1)[0];\n }\n }\n\n const sortedDiceConfigs = groupedConfigs\n .map(({ config, totalCount }) => ({\n ...config,\n count: totalCount,\n }))\n .sort((a, b) => {\n const aHasPriority = a.reroll > 0 || a.minimum > 0;\n const bHasPriority = b.reroll > 0 || b.minimum > 0;\n if (aHasPriority !== bHasPriority) return aHasPriority ? -1 : 1;\n if (b.sides !== a.sides) return b.sides - a.sides;\n return configComplexityScore(b) - configComplexityScore(a);\n });\n\n const diceConfigs = rootD20Group\n ? [\n { ...rootD20Group.config, count: rootD20Group.totalCount },\n ...sortedDiceConfigs,\n ]\n : sortedDiceConfigs;\n\n const totalModifier = this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n if (diceConfigs.length === 0) return totalModifier.toString();\n\n const rootDieConfig = this.getRootDieConfig();\n const newRootConfig = rootDieConfig\n ? diceConfigs.find((c) => rollConfigsEqual(c, rootDieConfig))\n : undefined;\n\n // Generate dice expressions without individual modifiers\n const diceExpressions = diceConfigs.map((config) =>\n this.configToSingleExpressionWithoutModifier(\n config,\n config === newRootConfig\n )\n );\n\n // Join dice expressions with appropriate operators based on their count\n let result = \"\";\n for (let i = 0; i < diceExpressions.length; i++) {\n const config = diceConfigs[i];\n const expression = diceExpressions[i];\n\n if (i === 0) {\n result = (config.isSubtraction ? \"-\" : \"\") + expression;\n\n // Add constants right after the root d20 die (if it's a d20)\n if (config.sides === 20 && totalModifier !== 0) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else result += ` - ${Math.abs(totalModifier)}`;\n }\n } else {\n // Use minus sign for negative subtraction, plus sign otherwise\n const operator = config.isSubtraction ? \" - \" : \" + \";\n result += operator + expression;\n }\n }\n\n // If constants weren't added after d20, add them at the end\n if (diceConfigs.length === 0 || diceConfigs[0].sides !== 20) {\n if (totalModifier > 0) result += ` + ${totalModifier}`;\n else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`;\n }\n\n return result.replace(/\\+ -/g, \"-\");\n }\n\n toPMF(eps: number = 0): PMF {\n // Main AST entry point\n return pmfFromRollBuilder(this, eps);\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n\n toAST(): ExpressionNode {\n const configs = this.getSubRollConfigs();\n return (\n astFromRollConfigs(configs) ||\n ({ type: \"constant\", value: 0 } as ExpressionNode)\n );\n }\n\n private configToSingleExpressionWithoutModifier(\n config: RollConfig,\n isRootDie: boolean\n ): string {\n if (!config.sides || config.sides <= 0) return \"\";\n\n let baseDie = `d${config.sides}`;\n\n if (config.reroll > 0) {\n if (config.minimum > 0 && config.explode > 0) {\n // Complex single roll case: minimum + explode + reroll\n // Apply reroll after minimum is applied\n } else if (config.minimum > 0) {\n // When there's a minimum but no explode, use descending order and apply before minimum\n for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`;\n } else {\n // When there's no minimum, use ascending order\n for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`;\n }\n }\n\n if (config.minimum > 0) {\n if (config.reroll > 0 && !config.explode) {\n baseDie = `${config.minimum}>(${baseDie})`;\n } else {\n baseDie = `${config.minimum}>${baseDie}`;\n }\n if (config.reroll > 0 && config.explode > 0) {\n for (let i = 1; i <= config.reroll; i++) {\n baseDie += ` reroll ${i}`;\n }\n }\n }\n\n // Check for hd20 shorthand AFTER adding explode\n if (baseDie === \"d20 reroll 1\" && config.minimum <= 1) baseDie = \"hd20\";\n\n let mainExpression = \"\";\n switch (config.rollType) {\n case \"advantage\":\n mainExpression = `${baseDie} > ${baseDie}`;\n break;\n case \"disadvantage\":\n mainExpression = `${baseDie} < ${baseDie}`;\n break;\n case \"elven accuracy\":\n mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`;\n break;\n case \"flat\":\n if (config.keep) {\n const mode = config.keep.mode === \"highest\" ? \"kh\" : \"kl\";\n const baseDieExpression =\n this.configToSingleExpressionWithoutModifier(\n {\n ...config,\n count: config.count,\n modifier: 0,\n rollType: \"flat\",\n keep: undefined,\n },\n false\n );\n mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDieExpression})`;\n } else {\n const isComplex = baseDie.length > `d${config.sides}`.length;\n const isHalflingShorthand = baseDie === \"hd20\";\n const isD20Shorthand = baseDie === \"d20\" && isRootDie;\n const hasMinimum = config.minimum > 0;\n const hasReroll = config.reroll > 0;\n // For negative subtraction, use absolute value for display\n // For negative counts from factory function, treat as 1 (legacy behavior)\n const effectiveCount = config.isSubtraction\n ? Math.abs(config.count)\n : config.count < 0\n ? 1\n : Math.abs(config.count);\n\n if (effectiveCount > 1) {\n const shouldAddParentheses = isComplex;\n mainExpression = shouldAddParentheses\n ? `${effectiveCount}(${baseDie})`\n : `${effectiveCount}${baseDie}`;\n } else if (effectiveCount === 1) {\n const needsParens = hasReroll && hasMinimum;\n if (config.isSubtraction) {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n } else if (\n isComplex ||\n isHalflingShorthand ||\n isD20Shorthand ||\n config.count < 0\n ) {\n mainExpression = needsParens ? `1(${baseDie})` : baseDie;\n } else {\n mainExpression = needsParens ? `1(${baseDie})` : `1${baseDie}`;\n }\n } else {\n mainExpression = baseDie;\n }\n }\n if (config.bestOf && config.count && config.bestOf < config.count) {\n mainExpression += `kh${config.bestOf}`;\n }\n break;\n }\n\n return mainExpression;\n }\n\n getRootDieConfig(): RollConfig | undefined {\n const configs = this.subRollConfigs;\n return configs.find((config) => config.sides > 0) || configs[0];\n }\n\n getAllDieConfigs(): readonly RollConfig[] {\n return this.getSubRollConfigs();\n }\n\n getBonusDiceConfigs(): RollConfig[] {\n const allConfigs = this.subRollConfigs;\n const rootConfig =\n allConfigs.find((config) => config.sides > 0) || allConfigs[0];\n if (!rootConfig) return [];\n return allConfigs\n .filter((config) => config.sides > 0)\n .filter((config) => config !== rootConfig);\n }\n\n getBonusDicePMFs(check: RollBuilder, eps: number = 0): PMF[] {\n return check\n .getBonusDiceConfigs()\n .map((config) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([config]), eps)\n );\n }\n\n get modifier(): number {\n return this.subRollConfigs.reduce(\n (sum, config) => sum + config.modifier,\n 0\n );\n }\n\n get rollType(): RollType {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.rollType || \"flat\";\n }\n\n get baseReroll(): number {\n const rootConfig = this.getRootDieConfig();\n return rootConfig?.reroll || 0;\n }\n\n half(): HalfRollBuilder {\n return new HalfRollBuilder(this);\n }\n\n /**\n * Scale this roll's result by `numerator / denominator`, rounding each outcome.\n * A general, composable form of {@link half} — used to model damage-type resistance\n * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`).\n * Compose several of these (and plain rolls) into one payload with {@link sumRolls}.\n */\n scaleResult(\n numerator: number,\n denominator: number = 1,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): ScaleRollBuilder {\n return new ScaleRollBuilder(this, numerator, denominator, rounding);\n }\n\n // Create a \"max of N rolls\" version of this roll for crit damage with keep operations\n maxOf(count: number): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this, count);\n }\n\n // These methods are implemented via prototype augmentation in ac.ts and dc.ts\n // They are declared here to provide proper TypeScript types\n ac(_targetAC: number): ACBuilder {\n throw new Error(\"ac() should be implemented via prototype augmentation\");\n }\n\n dc(_saveDC: number): DCBuilder {\n throw new Error(\"dc() should be implemented via prototype augmentation\");\n }\n}\n\nexport class HalfRollBuilder extends RollBuilder {\n constructor(private readonly innerRoll: RollBuilder) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n // No need to override create if we don't expose RollBuilder methods that use it,\n // but HalfRollBuilder extends RollBuilder so it does.\n // However, HalfRollBuilder seems to just wrap another roll.\n // If we call .plus() on HalfRollBuilder, it returns a HalfRollBuilder?\n // No, RollBuilder.plus returns RollBuilder.\n // The inheritance here is a bit tricky.\n // Existing code for HalfRollBuilder doesn't seem to implement plus/etc.\n // So .plus() on a HalfRollBuilder would return a RollBuilder (base class).\n // Which is fine.\n // The only issue is if we want it to return HalfRollBuilder, but it doesn't seem designed for that.\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const innerExpression = this.innerRoll.toExpression();\n return `(${innerExpression}) // 2`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"half\",\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): HalfRollBuilder {\n return new HalfRollBuilder(this.innerRoll.copy());\n }\n}\n\n/**\n * A roll whose result is scaled by `numerator / denominator` and rounded — the composable\n * generalization of {@link HalfRollBuilder}. Renders as `N * (inner)`, `(inner) // D`, or\n * `(inner) * N // D`. Terminal (like `half`): use {@link sumRolls} to combine with other rolls.\n */\nexport class ScaleRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly numerator: number,\n private readonly denominator: number = 1,\n private readonly rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n const inner = this.innerRoll.toExpression();\n if (this.denominator === 1) return `${this.numerator} * (${inner})`;\n if (this.numerator === 1) return `(${inner}) // ${this.denominator}`;\n return `(${inner}) * ${this.numerator} // ${this.denominator}`;\n }\n\n toAST(): ExpressionNode {\n return {\n type: \"scale\",\n numerator: this.numerator,\n denominator: this.denominator,\n rounding: this.rounding,\n child: this.innerRoll.toAST(),\n };\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): ScaleRollBuilder {\n return new ScaleRollBuilder(\n this.innerRoll.copy(),\n this.numerator,\n this.denominator,\n this.rounding\n );\n }\n}\n\nexport class MaxOfRollBuilder extends RollBuilder {\n constructor(\n private readonly innerRoll: RollBuilder,\n private readonly count: number,\n private readonly diceCount?: number,\n private readonly diceSides?: number\n ) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return this.innerRoll.hasHiddenState();\n }\n\n override get lastConfig(): RollConfig {\n // `lastConfig` is protected on the base class; reach it on the wrapped\n // instance via a typed view rather than `any`.\n return (this.innerRoll as unknown as { lastConfig: RollConfig }).lastConfig;\n }\n\n getSubRollConfigs(): readonly RollConfig[] {\n return this.innerRoll.getSubRollConfigs();\n }\n\n toExpression(): string {\n // Use the stored dice info to create the expression directly\n if (this.diceCount && this.diceSides) {\n return `max${this.count}(${this.diceCount}d${this.diceSides})`;\n }\n\n // If no stored dice info, fallback to simple max expression\n return `max${this.count}(?d?)`;\n }\n\n toAST(): ExpressionNode {\n // Use the stored dice info if available\n if (this.diceCount && this.diceSides) {\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: this.diceCount,\n child: { type: \"die\", sides: this.diceSides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n\n // Fallback: try to get from innerRoll\n try {\n const configs = this.innerRoll.getSubRollConfigs();\n if (configs.length === 1 && configs[0].sides) {\n const config = configs[0];\n const sumChild: ExpressionNode = {\n type: \"sum\",\n count: config.count,\n child: { type: \"die\", sides: config.sides },\n };\n return {\n type: \"maxOf\",\n count: this.count,\n child: sumChild,\n };\n }\n } catch {\n // Last resort: try parsing the expression (though this shouldn't work with current RollBuilder)\n }\n\n // Fallback - this shouldn't happen in normal usage\n throw new Error(\n `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`\n );\n }\n\n toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n copy(): MaxOfRollBuilder {\n return new MaxOfRollBuilder(this.innerRoll.copy(), this.count);\n }\n}\n\nexport class AlwaysHitBuilder extends RollBuilder {\n readonly attackConfig: CritConfig;\n\n constructor(baseRoll: RollBuilder, attackConfig?: CritConfig) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysHitBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(critThreshold: number): AlwaysHitBuilder {\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(this, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(this, undefined, true);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysHitBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold };\n return new AlwaysHitBuilder(baseCopy, newConfig);\n }\n}\n\nexport class AlwaysCritBuilder extends RollBuilder {\n readonly attackConfig: CritConfig & { ac?: number };\n readonly fromAlwaysHit: boolean;\n\n constructor(\n baseRoll: RollBuilder,\n attackConfig?: CritConfig & { ac?: number },\n fromAlwaysHit: boolean = false\n ) {\n if (baseRoll.hasHiddenState()) {\n throw new Error(\n \"Cannot create AlwaysCritBuilder from a roll with hidden state.\"\n );\n }\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig };\n } else {\n this.attackConfig = { critThreshold: 20 };\n }\n this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n critOn(critThreshold: number): AlwaysCritBuilder {\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs();\n return new RollBuilder(configs).toExpression();\n }\n\n override toPMF(): PMF {\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n return d20RollPMF(rollType, rerollOne);\n }\n\n override copy(): AlwaysCritBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const critThreshold = this.critThreshold;\n const newConfig = { critThreshold, ac: this.attackConfig.ac };\n return new AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit);\n }\n}\n\nexport class ParsedRollBuilder extends RollBuilder {\n private readonly cachedPMF: PMF;\n private readonly originalExpression: string;\n\n constructor(expression: string) {\n super([]); // Empty configs since we're bypassing the normal builder flow\n this.originalExpression = expression;\n this.cachedPMF = parse(expression, 0);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n protected create(configs: readonly RollConfig[]): RollBuilder {\n return new RollBuilder(configs);\n }\n\n override toPMF(_eps: number = 0): PMF {\n // Return the pre-computed PMF, ignoring epsilon for now\n // The parse() function was already called with eps=0\n return this.cachedPMF;\n }\n\n override toExpression(): string {\n return this.originalExpression;\n }\n\n override toAST(): ExpressionNode {\n // Since we don't have the actual AST structure, return a constant node\n // This is a limitation but shouldn't matter for terminal damage expressions\n throw new Error(\n \"ParsedRollBuilder does not support AST conversion. Use the builder API instead.\"\n );\n }\n\n override copy(): ParsedRollBuilder {\n return new ParsedRollBuilder(this.originalExpression);\n }\n\n override doubleDice(): ParsedRollBuilder {\n throw new Error(\n \"ParsedRollBuilder does not support doubleDice(). Use explicit onCrit() with the crit damage expression instead.\"\n );\n }\n}\n\nexport class PooledRollBuilder extends RollBuilder {\n constructor(\n private readonly baseAST: ExpressionNode,\n private readonly baseExpression: string,\n configs: readonly RollConfig[] = []\n ) {\n // Initialize with empty config if none provided\n super(configs.length > 0 ? configs : 0);\n }\n\n protected create(configs: readonly RollConfig[]): PooledRollBuilder {\n // This is the key fix: we preserve the baseAST and baseExpression\n // and only update the configs\n return new PooledRollBuilder(this.baseAST, this.baseExpression, configs);\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override d(_sides: number | undefined): RollBuilder {\n throw new Error(\"Cannot add dice to a pooled roll. The pool is finalized.\");\n }\n\n override reroll(_value: number): RollBuilder {\n throw new Error(\"Cannot set reroll on a pooled roll.\");\n }\n\n override explode(_count: number | undefined = Infinity): RollBuilder {\n throw new Error(\"Cannot set explode on a pooled roll.\");\n }\n\n override minimum(_val: number | undefined): RollBuilder {\n throw new Error(\"Cannot set minimum on a pooled roll.\");\n }\n\n override bestOf(_count: number | undefined): RollBuilder {\n throw new Error(\"Cannot set bestOf on a pooled roll.\");\n }\n\n override keepHighest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepHighest on a pooled roll. Use keepHighestAll again if you want nested pooling.\"\n );\n }\n\n override keepLowest(_total: number, _count: number): RollBuilder {\n throw new Error(\n \"Cannot use keepLowest on a pooled roll. Use keepLowestAll again if you want nested pooling.\"\n );\n }\n\n override withAdvantage(): RollBuilder {\n throw new Error(\"Cannot set advantage on a pooled roll.\");\n }\n\n override withDisadvantage(): RollBuilder {\n throw new Error(\"Cannot set disadvantage on a pooled roll.\");\n }\n\n override withElvenAccuracy(): RollBuilder {\n throw new Error(\"Cannot set elven accuracy on a pooled roll.\");\n }\n\n override toAST(): ExpressionNode {\n const configsAST = super.toAST();\n\n // Check if configsAST is effectively zero/empty\n const isZero = configsAST.type === \"constant\" && configsAST.value === 0;\n\n if (isZero) {\n return this.baseAST;\n }\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [\n { node: this.baseAST, sign: 1 },\n { node: configsAST, sign: 1 },\n ];\n\n return { type: \"add\", children };\n }\n\n override toExpression(): string {\n const configsExpression = super.toExpression();\n\n // If no configs added, just return base expression\n if (configsExpression === \"0\") {\n return this.baseExpression;\n }\n\n // Clean up the join\n if (configsExpression.startsWith(\"-\")) {\n // If it's a negative number/expression, format as \" - value\"\n // configsExpression is like \"-2\" or \"-1d6\"\n return `${this.baseExpression} - ${configsExpression.substring(1)}`;\n }\n return `${this.baseExpression} + ${configsExpression}`;\n }\n\n override copy(): PooledRollBuilder {\n return new PooledRollBuilder(\n this.baseAST,\n this.baseExpression,\n this.getSubRollConfigs()\n );\n }\n\n override scaleDice(scale: number): RollBuilder {\n const scaleInt = Math.floor(scale);\n if (scaleInt !== scale) throw new Error(\"Scale must be an integer\");\n if (scaleInt <= 0) throw new Error(\"Scale must be > 0\");\n\n // Scale the base pool (treat it as a die/unit)\n // We wrap the base AST in a SumNode\n const newBaseAST: SumNode = {\n type: \"sum\",\n count: scaleInt,\n child: this.baseAST,\n };\n const newBaseExpr =\n scaleInt === 1\n ? this.baseExpression\n : `${scaleInt}(${this.baseExpression})`;\n\n // We preserve the existing modifiers (subRollConfigs) without scaling them,\n // because scaleDice() generally only scales \"dice\", not flat modifiers.\n // Since we forbid adding dice to PooledRollBuilder, subRollConfigs are only modifiers.\n return new PooledRollBuilder(\n newBaseAST,\n newBaseExpr,\n this.getSubRollConfigs()\n );\n }\n\n times(count: number): PooledRollBuilder {\n if (isNaN(count)) throw new Error(\"Invalid NaN value for times\");\n if (Math.floor(count) !== count)\n throw new Error(\"times() requires an integer\");\n if (count < 0) throw new Error(\"times() requires a non-negative integer\");\n\n // We wrap the current state (base + modifiers) into a new pool repeated N times\n const currentAST = this.toAST();\n const currentExpr = this.toExpression();\n\n const sumNode: SumNode = {\n type: \"sum\",\n count,\n child: currentAST,\n };\n\n const newExpr = count === 1 ? currentExpr : `${count}(${currentExpr})`;\n\n return new PooledRollBuilder(sumNode, newExpr);\n }\n}\n\n/**\n * An additive composite of independent rolls that preserves each part's AST — the piece\n * that lets a scaled/halved sub-roll (which the flat `.plus()` merge would otherwise drop)\n * sit beside plain rolls in one damage payload. Its PMF convolves the parts; its expression\n * joins them with ` + `. Built via {@link sumRolls}; terminal (used as an onHit/onCrit/\n * onSaveFailure payload), so it reports hidden state to reject accidental flat merges.\n */\nclass CompositeSumRollBuilder extends RollBuilder {\n constructor(private readonly parts: readonly RollBuilder[]) {\n super(0); // dummy, we override methods\n }\n\n override hasHiddenState(): boolean {\n return true;\n }\n\n override getSubRollConfigs(): readonly RollConfig[] {\n return [];\n }\n\n override toAST(): ExpressionNode {\n return {\n type: \"add\",\n children: this.parts.map((p) => ({\n node: p.toAST(),\n sign: 1 as const,\n })),\n };\n }\n\n override toExpression(): string {\n const exprs = this.parts\n .map((p) => p.toExpression())\n .filter((e) => e && e !== \"0\");\n if (exprs.length === 0) return \"0\";\n let result = exprs[0];\n for (let i = 1; i < exprs.length; i++) {\n const e = exprs[i];\n result += e.startsWith(\"-\") ? ` - ${e.substring(1)}` : ` + ${e}`;\n }\n return result.replace(/\\+ -/g, \"-\");\n }\n\n override toPMF(eps: number = 0): PMF {\n return pmfFromRollBuilder(this, eps);\n }\n\n override copy(): CompositeSumRollBuilder {\n return new CompositeSumRollBuilder(this.parts.map((p) => p.copy()));\n }\n}\n\n/**\n * Combine several rolls into one additive payload whose PMF is their convolution and whose\n * expression is them joined with ` + `. Unlike `a.plus(b)`, this preserves parts that carry\n * hidden state (e.g. `roll.scaleResult(1, 2)` / `roll.half()`), so per-damage-type resistance\n * and vulnerability survive into both the distribution and the rendered expression.\n * Empty parts collapse to `0`; a single part is returned unwrapped.\n */\nexport function sumRolls(parts: readonly RollBuilder[]): RollBuilder {\n const meaningful = parts.filter((p): p is RollBuilder => p !== undefined);\n if (meaningful.length === 0) return new RollBuilder(0);\n if (meaningful.length === 1) return meaningful[0];\n return new CompositeSumRollBuilder(meaningful);\n}\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { RollBuilder } from \"./roll\";\nimport type { RollFactory } from \"./types\";\n\nconst rollFn = (\n count: number,\n sidesOrDie?: number | RollBuilder,\n modifier?: number\n): RollBuilder => {\n if (sidesOrDie instanceof RollBuilder) {\n if (sidesOrDie.hasHiddenState()) {\n throw new Error(\n \"Cannot use a roll with hidden state (like a pooled roll) as a die type.\"\n );\n }\n // roll(2, d6, 5)\n // Create a new config, using the base die's config but overriding the count\n const subRollConfigs = sidesOrDie.getSubRollConfigs();\n if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier);\n\n const absCount = Math.abs(count);\n\n const newConfigs = subRollConfigs.map((config) => ({\n ...config,\n count: config.count * absCount,\n modifier: config.modifier * absCount,\n }));\n\n let resultBuilder = new RollBuilder(newConfigs);\n\n if (count < 0) {\n const negatedConfigs = resultBuilder\n .getSubRollConfigs()\n .map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));\n resultBuilder = new RollBuilder(negatedConfigs);\n }\n\n return resultBuilder.plus(modifier);\n } else {\n // roll(2, 6, 5)\n let builder = new RollBuilder(count);\n if (sidesOrDie && sidesOrDie > 0) {\n builder = builder.d(sidesOrDie);\n }\n return builder.plus(modifier);\n }\n};\n\nrollFn.d = (sides: number | string): RollBuilder => {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n};\nrollFn.hd20 = (): RollBuilder => new RollBuilder(1).d20().reroll(1);\nrollFn.d4 = (): RollBuilder => new RollBuilder(1).d4();\nrollFn.d6 = (): RollBuilder => new RollBuilder(1).d6();\nrollFn.d8 = (): RollBuilder => new RollBuilder(1).d8();\nrollFn.d10 = (): RollBuilder => new RollBuilder(1).d10();\nrollFn.d12 = (): RollBuilder => new RollBuilder(1).d12();\nrollFn.d20 = (): RollBuilder => new RollBuilder(1).d20();\nrollFn.d100 = (): RollBuilder => new RollBuilder(1).d100();\nrollFn.flat = (n: number): RollBuilder => new RollBuilder(0).plus(n);\n\nexport function d(sides: number | string): RollBuilder {\n if (typeof sides === \"string\") {\n return RollBuilder.fromArgs(sides);\n }\n return new RollBuilder(1).d(sides);\n}\n\nexport const d4 = new RollBuilder(1).d4();\nexport const d6 = new RollBuilder(1).d6();\nexport const d8 = new RollBuilder(1).d8();\nexport const d10 = new RollBuilder(1).d10();\nexport const d12 = new RollBuilder(1).d12();\nexport const d20 = new RollBuilder(1).d20();\nexport const hd20 = new RollBuilder(1).d20().reroll(1);\nexport const d100 = new RollBuilder(1).d100();\nexport const flat = (n: number) => new RollBuilder(0).plus(n);\n\nexport const roll: RollFactory = rollFn as RollFactory;\n\nexport const builderPMFCache = new LRUCache(1000);\n","import { LRUCache, PMF } from \"../\";\nimport { d20RollPMF } from \"./d20\";\nimport { builderPMFCache } from \"./factory\";\nimport type {\n AddNode,\n ConstantNode,\n D20RollNode,\n DieNode,\n ExpressionNode,\n KeepNode,\n MaxOfNode,\n SumNode,\n} from \"./nodes\";\nimport type { RollBuilder } from \"./roll\";\nimport type { RollConfig } from \"./types\";\n\n// For now, default to 0 epsilon. Later we can tighten to EPS.\nconst defaultEps = 0;\n\nconst singleDiePMFCache = new LRUCache(1000);\n\nexport function astFromRollConfigs(\n configs: readonly RollConfig[]\n): ExpressionNode | undefined {\n // TODO add cache for this\n if (!configs || configs.length === 0) return undefined;\n\n const children: { node: ExpressionNode; sign: 1 | -1 }[] = [];\n let constantSum = 0;\n\n for (const cfg of configs) {\n const sign: 1 | -1 = cfg.isSubtraction || cfg.count < 0 ? -1 : 1;\n const count = Math.abs(cfg.count || 0);\n\n constantSum += cfg.modifier || 0;\n\n if ((cfg.sides || 0) <= 0) continue;\n\n const die: DieNode = {\n type: \"die\",\n sides: cfg.sides,\n reroll: cfg.reroll > 0 ? cfg.reroll : undefined,\n minimum: cfg.minimum > 0 ? cfg.minimum : undefined,\n explode:\n cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0\n ? cfg.explode\n : undefined,\n };\n\n let node: ExpressionNode = die;\n\n let appliedRollType = false;\n if (cfg.rollType && cfg.rollType !== \"flat\") {\n if (cfg.sides === 20) {\n node = {\n type: \"d20Roll\",\n rollType: cfg.rollType,\n child: node,\n } as D20RollNode;\n } else {\n const n = cfg.rollType === \"elven accuracy\" ? 3 : 2;\n const mode = cfg.rollType === \"disadvantage\" ? \"lowest\" : \"highest\";\n const base: SumNode = { type: \"sum\", count: n, child: node };\n node = { type: \"keep\", mode, count: 1, child: base } as KeepNode;\n }\n appliedRollType = true;\n }\n\n if (cfg.rollType === \"flat\" && cfg.keep && cfg.keep.total > 0) {\n const baseCount = Math.max(1, Math.floor(Math.abs(count || 1)));\n const trials = Math.max(1, Math.floor(cfg.keep.total));\n const k = Math.max(0, Math.floor(cfg.keep.count));\n\n // For keep-highest of 1, always treat as trials-of-sums: max over trial sums\n if (k === 1 && cfg.keep.mode === \"highest\") {\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n node = {\n type: \"maxOf\",\n count: trials,\n child: perTrial,\n } as MaxOfNode;\n }\n } else if (trials === baseCount) {\n // Classic pool: keep K of N faces from N iid dice\n const base: SumNode = { type: \"sum\", count: trials, child: node };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: base,\n } as KeepNode;\n } else {\n // General trials-of-sums: trials of (baseCount dice sum), keep K trial sums\n const perTrial: SumNode = {\n type: \"sum\",\n count: baseCount,\n child: node,\n };\n if (trials === 1) {\n node = perTrial;\n } else {\n const trialPool: SumNode = {\n type: \"sum\",\n count: trials,\n child: perTrial,\n };\n node = {\n type: \"keep\",\n mode: cfg.keep.mode,\n count: k,\n child: trialPool,\n } as KeepNode;\n }\n }\n } else {\n const c = appliedRollType ? 1 : Math.max(1, count || 1);\n node = { type: \"sum\", count: c, child: node } as SumNode;\n }\n\n children.push({ node, sign });\n }\n\n if (children.length === 0) {\n return { type: \"constant\", value: constantSum } as ConstantNode;\n }\n\n const add: AddNode = { type: \"add\", children };\n if (constantSum !== 0)\n add.children.push({\n node: { type: \"constant\", value: constantSum },\n sign: 1,\n });\n return add;\n}\n\nexport function resolve(node: ExpressionNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(node);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n const result = ((): PMF => {\n switch (node.type) {\n case \"constant\":\n return PMF.delta(node.value, eps);\n\n case \"die\": {\n return resolveSingleDie(node, eps);\n }\n\n case \"sum\": {\n const base = resolve(node.child, eps);\n const n = Math.max(0, Math.floor(node.count));\n if (n === 0) return PMF.delta(0, eps);\n if (n === 1) return base;\n return base.power(n, eps);\n }\n\n case \"add\": {\n let shift = 0;\n const parts: PMF[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n shift += c.sign * c.node.value;\n } else {\n const p = resolve(c.node, eps);\n parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v));\n }\n }\n if (parts.length === 0) return PMF.delta(shift, eps);\n let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps);\n if (shift !== 0) res = res.mapDamage((v) => v + shift);\n return res;\n }\n\n case \"keep\": {\n const totalTrials = getTotalCount(node);\n const keepCount = Math.max(0, Math.min(node.count, totalTrials));\n if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps);\n\n // Resolve the per-trial PMF (the child of the Sum inside Keep)\n const perTrialNode = node.child.child; // Sum(child: perTrial)\n const perTrialPMF = resolve(perTrialNode, eps);\n\n return keepSumPMF(\n perTrialPMF,\n totalTrials,\n keepCount,\n node.mode === \"highest\",\n eps\n );\n }\n\n case \"d20Roll\": {\n const childDie = findDie(node.child);\n const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;\n return d20RollPMF(node.rollType, rerollOne);\n }\n\n case \"half\": {\n const childPMF = resolve(node.child, eps);\n return childPMF.scaleDamage(0.5, \"floor\");\n }\n\n case \"maxOf\": {\n const childPMF = resolve(node.child, eps);\n const count = Math.max(1, Math.floor(node.count));\n if (count === 1) return childPMF;\n\n // Compute the maximum of count independent rolls of childPMF\n return computeMaxOfPMF(childPMF, count, eps);\n }\n\n case \"scale\": {\n const childPMF = resolve(node.child, eps);\n const denom = node.denominator === 0 ? 1 : node.denominator;\n return childPMF.scaleDamage(node.numerator / denom, node.rounding);\n }\n }\n })();\n\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function pmfFromRollBuilder(\n rb: RollBuilder,\n eps: number = defaultEps\n): PMF {\n const ast = rb.toAST();\n return resolve(ast, eps);\n}\n\nfunction resolveSingleDie(die: DieNode, eps: number = defaultEps): PMF {\n const signature = getASTSignature(die);\n const cacheKey = `${signature}_${eps}`;\n\n const cached = singleDiePMFCache.get(cacheKey);\n if (cached) return cached;\n\n const s = Math.max(0, Math.floor(die.sides));\n if (s <= 0) return PMF.delta(0, eps);\n\n let probs = new Map();\n for (let v = 1; v <= s; v++) probs.set(v, 1 / s);\n\n // TODO - check if this is correct. Sequential reroll passes? Or at once?\n const r = Math.max(0, Math.floor(die.reroll || 0));\n if (r > 0) {\n const k = Math.min(r, s);\n const rerollMass = k / s; // total probability rerolled once\n const uniformReroll = rerollMass / s; // mass added to each face from reroll\n const next = new Map();\n for (let v = 1; v <= s; v++) {\n const keep = v <= k ? 0 : 1 / s;\n next.set(v, keep + uniformReroll);\n }\n probs = next;\n }\n\n let pmf = PMF.fromMap(new Map(probs), eps);\n\n // Minimum per die\n const minV = Math.max(0, Math.floor(die.minimum || 0));\n if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV));\n\n // Exploding dice (finite) on max face only\n const explode = die.explode;\n if (explode && Number.isFinite(explode) && explode > 0) {\n const times = Math.floor(explode);\n const maxFace = s;\n\n // Split pmf into non-max and max\n const nonMax = new Map();\n const pMax = pmf.pAt(maxFace);\n for (const v of pmf.support()) {\n if (v !== maxFace) nonMax.set(v, pmf.pAt(v));\n }\n let nonMaxPMF = PMF.fromMap(nonMax, eps);\n if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) {\n // keep raw mass composition\n nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax);\n }\n\n // TODO - explosions\n // Additional roll distribution equals original pmf without explosions applied again.\n // For simplicity we treat cascaded explosions as adding uniform max-only triggers.\n // Compute sum of up to `times` additional rolls conditioned on each explosion hit.\n let tail = PMF.delta(0, eps);\n const addOnce = pmf;\n for (let t = 1; t <= times; t++) {\n tail = tail.convolve(addOnce, eps);\n }\n // Mix: with prob (1 - pMax) take nonMax, with prob pMax take maxFace + tail\n const exploded = PMF.branch(\n tail.mapDamage((v) => v + maxFace),\n nonMaxPMF,\n pMax\n );\n pmf = exploded;\n }\n\n singleDiePMFCache.set(cacheKey, pmf);\n return pmf;\n}\n\n// Getters\n\nfunction findDie(node: ExpressionNode): DieNode | undefined {\n switch (node.type) {\n case \"die\":\n return node;\n case \"constant\":\n return undefined;\n case \"sum\":\n case \"d20Roll\":\n case \"half\":\n case \"maxOf\":\n case \"scale\":\n return findDie(node.child);\n case \"keep\":\n return findDie(node.child.child);\n case \"add\":\n for (const c of node.children) {\n const d = findDie(c.node);\n if (d) return d;\n }\n return undefined;\n }\n}\n\nfunction getTotalCount(node: KeepNode): number {\n // The total dice count is encoded in the nearest SumNode under child\n let cur = node.child;\n while (cur.type === \"keep\") cur = cur.child;\n return cur.type === \"sum\" ? Math.max(0, Math.floor(cur.count)) : 0;\n}\n\nfunction computeMaxOfPMF(\n pmf: PMF,\n count: number,\n eps: number = defaultEps\n): PMF {\n // Compute the maximum of 'count' independent rolls of the given PMF\n if (count <= 1) return pmf;\n\n const support = pmf.support();\n const out = new Map();\n\n // For small counts, we can enumerate all outcomes\n if (count <= 6 && support.length <= 20) {\n function dfs(\n rollsLeft: number,\n currentMax: number,\n probability: number\n ): void {\n if (rollsLeft === 0) {\n out.set(currentMax, (out.get(currentMax) || 0) + probability);\n return;\n }\n\n for (const value of support) {\n const p = pmf.pAt(value);\n if (p > 0) {\n const newMax = Math.max(currentMax, value);\n dfs(rollsLeft - 1, newMax, probability * p);\n }\n }\n }\n\n dfs(count, -Infinity, 1);\n } else {\n // For larger cases, use the CDF method. Walk the sorted support once while\n // accumulating a running CDF, so each P(max = v) costs O(1) instead of a\n // full-map cdfAt() scan (previously O(N) per value → O(N²) overall).\n // Between two consecutive support points there is no probability mass, so\n // the running CDF up to (but not including) v equals cdfAt(v - 1).\n const sortedSupport = [...support].sort((a, b) => a - b);\n let runningCdf = 0;\n for (const value of sortedSupport) {\n const prevCdf = runningCdf;\n runningCdf += pmf.pAt(value);\n\n // P(max = value) = P(all rolls <= value) - P(all rolls <= value-1)\n const probMax = Math.pow(runningCdf, count) - Math.pow(prevCdf, count);\n if (probMax > eps) {\n out.set(value, probMax);\n }\n }\n }\n\n return PMF.fromMap(out, eps);\n}\n\nfunction keepSumPMF(\n single: PMF,\n total: number,\n keep: number,\n highest: boolean,\n eps: number = defaultEps\n): PMF {\n // Trivial/fast paths\n if (keep >= total) return single.power(total, eps);\n if (keep <= 0) return PMF.delta(0, eps);\n\n const sortedSupport = [...single.support()].sort((a, b) => a - b);\n const pmfSig = sortedSupport\n .map((val) => `${val}:${single.pAt(val).toPrecision(6)}`)\n .join(\",\");\n const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${\n highest ? 1 : 0\n }|e:${eps}`;\n\n const cached = builderPMFCache.get(cacheKey);\n if (cached) return cached;\n\n // kh1/kl1 fast paths using max-of machinery\n if (keep === 1) {\n if (highest) {\n return computeMaxOfPMF(single, total, eps);\n } else {\n // min of n i.i.d. == -max of n of negated variable\n const neg = single.mapDamage((v) => -v);\n const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v);\n builderPMFCache.set(cacheKey, minPMF);\n return minPMF;\n }\n }\n\n // DP over descending values; state = (used, remainingTrials) → map(sum -> prob)\n // Transition by drawing X occurrences at current value v from remainingTrials r: X ~ Binom(r, p)\n // Select t = min(X, keep - used) into the sum (highest picks first), then continue with r - X.\n\n type SumMap = Map;\n let state: Map = new Map();\n // Pack the (used, remainingTrials) state into a single integer key instead of\n // a \"used|r\" string. r ∈ [0, total], so a stride of (total + 1) is collision\n // free, and decoding is plain integer math — no split()/parseInt() per\n // transition in the hot loop. Behavior is identical (same states, same order).\n const stride = total + 1;\n const keyOf = (used: number, r: number) => used * stride + r;\n\n state.set(keyOf(0, total), new Map([[0, 1]]));\n\n const valuesDesc = highest\n ? [...sortedSupport].sort((a, b) => b - a)\n : [...sortedSupport].sort((a, b) => a - b);\n\n const binomPMF = (r: number, p: number): number[] => {\n if (r <= 0) return [1];\n if (p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[0] = 1;\n return arr;\n }\n if (1 - p <= eps) {\n const arr = new Array(r + 1).fill(0);\n arr[r] = 1;\n return arr;\n }\n const q = 1 - p;\n const arr = new Array(r + 1).fill(0);\n\n // stable recurrence from k=0\n arr[0] = Math.pow(q, r);\n const ratio = p / q;\n for (let x = 1; x <= r; x++)\n arr[x] = ((arr[x - 1] * (r - x + 1)) / x) * ratio;\n\n // Normalize minor drift\n let s = 0;\n for (let x = 0; x <= r; x++) s += arr[x];\n if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s;\n\n return arr;\n };\n\n const pruneMap = (m: SumMap, threshold: number): SumMap => {\n if (threshold <= 0) return m;\n const out = new Map();\n for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr);\n return out.size === m.size ? m : out;\n };\n\n const pruneState = (st: Map, threshold: number) => {\n if (threshold <= 0) return st;\n const out = new Map();\n for (const [k, m] of st) {\n const mm = pruneMap(m, threshold);\n if (mm.size > 0) out.set(k, mm);\n }\n return out;\n };\n\n let processedMass = 0;\n for (const v of valuesDesc) {\n const p = single.pAt(v);\n if (p <= 0) continue;\n const q = Math.max(eps, 1 - processedMass);\n const pCond = Math.min(1, p / q);\n const next: Map = new Map();\n\n for (const [k, m] of state) {\n const used = Math.floor(k / stride);\n const r = k - used * stride;\n if (r === 0) {\n // No trials left; carry state forward unchanged\n const destKey = keyOf(used, 0);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr);\n next.set(destKey, dest);\n continue;\n }\n\n const bin = binomPMF(r, pCond);\n const remainingCapacity = keep - used;\n\n for (let x = 0; x <= r; x++) {\n const px = bin[x];\n if (px <= eps) continue;\n const t = Math.min(x, remainingCapacity);\n const used2 = used + t;\n const r2 = r - x;\n const add = t * v;\n\n const destKey = keyOf(used2, r2);\n const dest = next.get(destKey) ?? new Map();\n for (const [sum, pr] of m) {\n const s2 = sum + add;\n const prob = pr * px;\n const cur = dest.get(s2) || 0;\n const nv = cur + prob;\n if (nv >= eps) dest.set(s2, nv);\n }\n if (dest.size > 0) next.set(destKey, dest);\n }\n }\n\n // Light pruning proportional to eps\n state = pruneState(next, eps * 1e-6);\n processedMass += p;\n }\n\n // Collect results where all trials assigned and exactly keep were used\n const finalKey = keyOf(keep, 0);\n const dist = state.get(finalKey) ?? new Map();\n\n if (dist.size === 0) {\n // Fallback safety: return empty mass (should not happen)\n return PMF.emptyMass();\n }\n\n const result = PMF.fromMap(dist, eps);\n builderPMFCache.set(cacheKey, result);\n return result;\n}\n\nexport function getASTSignature(node: ExpressionNode): string {\n switch (node.type) {\n case \"constant\":\n return `c:${node.value}`;\n case \"die\": {\n // Use a fixed order for properties to ensure a stable signature.\n const parts: string[] = [];\n parts.push(`s:${node.sides}`);\n if (node.reroll) parts.push(`r:${node.reroll}`);\n if (node.minimum) parts.push(`m:${node.minimum}`);\n if (node.explode) parts.push(`e:${node.explode}`);\n return `d{${parts.join(\",\")}}`;\n }\n case \"sum\":\n return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"d20Roll\":\n return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`;\n case \"keep\":\n return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(\n node.child\n )}}`;\n case \"half\":\n return `half{ch:${getASTSignature(node.child)}}`;\n case \"maxOf\":\n return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;\n case \"scale\":\n return `scale{n:${node.numerator},d:${node.denominator},r:${\n node.rounding\n },ch:${getASTSignature(node.child)}}`;\n case \"add\": {\n let constantValue = 0;\n const otherChildrenSigs: string[] = [];\n for (const c of node.children) {\n if (c.node.type === \"constant\") {\n constantValue += c.sign * c.node.value;\n } else {\n otherChildrenSigs.push(\n `${c.sign === -1 ? \"-\" : \"+\"}${getASTSignature(c.node)}`\n );\n }\n }\n\n if (constantValue !== 0) {\n otherChildrenSigs.push(\n constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}`\n );\n }\n\n // Sort to handle commutative nature of addition.\n otherChildrenSigs.sort();\n\n return `add[${otherChildrenSigs.join(\"\")}]`;\n }\n }\n}\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport type { ACBuilder } from \"./ac\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport {\n AlwaysCritBuilder,\n AlwaysHitBuilder,\n ParsedRollBuilder,\n RollBuilder,\n} from \"./roll\";\nimport type { AttackResolution, CheckBuilder } from \"./types\";\n\ntype ActionEffect = RollBuilder;\n\nexport class AttackBuilder implements CheckBuilder {\n constructor(\n readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n private readonly hitEffect?: ActionEffect,\n private readonly critEffect?: ActionEffect | null,\n private readonly missEffect?: ActionEffect\n ) {}\n\n onCrit(val: number): AttackBuilder;\n onCrit(val: string): AttackBuilder;\n onCrit(val: RollBuilder): AttackBuilder;\n onCrit(count: number, die: RollBuilder): AttackBuilder;\n onCrit(count: number, sides: number): AttackBuilder;\n onCrit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onCrit(count: number, sides: number, modifier: number): AttackBuilder;\n onCrit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n damageRoll,\n this.missEffect\n );\n }\n\n onMiss(val: number): AttackBuilder;\n onMiss(val: string): AttackBuilder;\n onMiss(val: RollBuilder): AttackBuilder;\n onMiss(count: number, die: RollBuilder): AttackBuilder;\n onMiss(count: number, sides: number): AttackBuilder;\n onMiss(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onMiss(count: number, sides: number, modifier: number): AttackBuilder;\n onMiss(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(\n this.check,\n this.hitEffect,\n this.critEffect,\n damageRoll\n );\n }\n\n noCrit(): AttackBuilder {\n return new AttackBuilder(this.check, this.hitEffect, null, this.missEffect);\n }\n\n // Legacy expressions\n toExpression(): string {\n const checkPart = this.check.toExpression();\n\n let effectPart = \"\";\n\n if (this.hitEffect) {\n effectPart = `(${this.hitEffect.toExpression()})`;\n if (this.critEffect !== null) {\n let crit: RollBuilder;\n if (this.critEffect) {\n crit = this.critEffect;\n } else {\n // For ParsedRollBuilder, we can't double dice, so skip the crit expression\n if (this.hitEffect instanceof ParsedRollBuilder) {\n // Don't try to double ParsedRollBuilder - leave it out of expression\n crit = RollBuilder.fromArgs(0);\n } else {\n crit =\n this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);\n }\n }\n\n const critThreshold = this.check.critThreshold;\n if (critThreshold < 1 || critThreshold > 20) {\n throw new Error(\n `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.`\n );\n }\n\n // Only include crit expression if crit is not zero\n const critExpression = crit.toExpression();\n if (critExpression !== \"0\") {\n if (critThreshold === 20) {\n effectPart += ` crit (${critExpression})`;\n } else {\n const xcritNumber = 21 - critThreshold;\n effectPart += ` xcrit${xcritNumber} (${critExpression})`;\n }\n }\n }\n\n if (this.missEffect) {\n effectPart += ` miss (${this.missEffect.toExpression()})`;\n }\n }\n\n return `${checkPart} * ${effectPart}`;\n }\n\n resolveProbabilities(\n check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder,\n eps: number = 0\n ): { pSuccess: number; pHit: number; pCrit: number; pMiss: number } {\n const rollType = check.rollType;\n const rerollOne = check.baseReroll > 0;\n\n const critThreshold = check.critThreshold;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n if (check instanceof AlwaysCritBuilder) {\n // If fromAlwaysHit is true, everything is a crit (no misses)\n if (check.fromAlwaysHit) {\n return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };\n }\n\n // If fromAlwaysHit is false (came from ACBuilder), we need to check AC\n // Natural 1s always miss, everything else that would hit becomes a crit\n const ac = check.attackConfig.ac ?? 0;\n const staticMod = this.check.modifier;\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Natural 1 always misses\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Check if this roll would hit the AC\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n // Everything that hits becomes a crit\n pcrit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n return { pSuccess: pcrit, pHit: 0, pCrit: pcrit, pMiss: pmiss };\n }\n\n if (check instanceof AlwaysHitBuilder) {\n // Preserve rollType for crit odds\n let pCrit = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n if (r >= critThreshold) pCrit += pr;\n }\n const pHit = 1 - pCrit;\n const pMiss = 0;\n\n return { pSuccess: 1, pHit, pCrit, pMiss };\n }\n\n const ac = check.attackConfig.ac;\n const staticMod = this.check.modifier;\n\n const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let pcrit = 0;\n let phit = 0;\n let pmiss = 0;\n\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n\n // Handle auto-miss\n if (r === 1) {\n pmiss += pr;\n continue;\n }\n\n // Handle crit\n if (r >= critThreshold) {\n pcrit += pr;\n continue;\n }\n\n // Handle normal hit/miss\n const need = ac - staticMod - r;\n const pBonusHit = bonusPMF.tailProbGE(need);\n\n phit += pr * pBonusHit;\n pmiss += pr * (1 - pBonusHit);\n }\n\n const psuccess = phit + pcrit;\n return { pSuccess: psuccess, pHit: phit, pCrit: pcrit, pMiss: pmiss };\n }\n\n resolve(eps: number = EPS): AttackResolution {\n const {\n pHit,\n pCrit,\n pMiss: pmiss,\n } = this.resolveProbabilities(this.check, eps);\n const hitPMF = this.hitEffect\n ? this.hitEffect instanceof ParsedRollBuilder\n ? this.hitEffect.toPMF(eps)\n : pmfFromRollBuilder(this.hitEffect, eps)\n : PMF.delta(0, eps);\n\n let critPMF: PMF | null = null;\n let phit = pHit;\n let pcrit = pCrit;\n\n if (this.critEffect === null) {\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n } else {\n let critBuilder: RollBuilder | undefined;\n \n if (this.critEffect) {\n critBuilder = this.critEffect;\n } else if (this.hitEffect instanceof ParsedRollBuilder) {\n // For ParsedRollBuilder, we can't automatically double dice\n // So treat it as noCrit() - roll crit probability into hit\n critPMF = null;\n phit += pcrit;\n pcrit = 0;\n critBuilder = undefined;\n } else {\n critBuilder = this.hitEffect?.copy().doubleDice();\n }\n\n if (critBuilder) {\n critPMF = critBuilder instanceof ParsedRollBuilder\n ? critBuilder.toPMF(eps)\n : pmfFromRollBuilder(critBuilder, eps);\n }\n }\n const missPMF = this.missEffect\n ? this.missEffect instanceof ParsedRollBuilder\n ? this.missEffect.toPMF(eps)\n : pmfFromRollBuilder(this.missEffect, eps)\n : PMF.delta(0, eps);\n\n // Mix them up\n const mix = new Mixture(eps);\n if (phit > 0) mix.add(\"hit\", hitPMF, phit);\n if (critPMF && pcrit > 0) mix.add(\"crit\", critPMF, pcrit);\n if (pmiss > 0)\n mix.add(this.missEffect ? \"missDamage\" : \"missNone\", missPMF, pmiss);\n\n return {\n pmf: mix.buildPMF(eps) ?? PMF.delta(0, eps),\n check: this.check.toPMF(eps) ?? PMF.delta(0, eps),\n hit: hitPMF ?? PMF.delta(0, eps),\n crit: critPMF ?? PMF.delta(0, eps),\n miss: missPMF ?? PMF.delta(0, eps),\n weights: { hit: phit, crit: pcrit, miss: pmiss },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { AttackBuilder } from \"./attack\";\nimport { d20RollPMF } from \"./d20\";\nimport { AlwaysCritBuilder, RollBuilder } from \"./roll\";\n\nexport interface AttackConfig {\n ac: number;\n critThreshold: number;\n}\nexport class ACBuilder extends RollBuilder {\n readonly attackConfig: AttackConfig;\n\n constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig) {\n super(baseRoll.getSubRollConfigs());\n\n if (attackConfig) {\n this.attackConfig = { ...attackConfig, ac };\n } else {\n this.attackConfig = { ac, critThreshold: 20 };\n }\n }\n\n // onHit(effect: RollBuilder): AttackBuilder {\n // return new AttackBuilder(this).onHit(effect)\n // }\n\n onHit(val: number): AttackBuilder;\n onHit(val: string): AttackBuilder;\n onHit(val: RollBuilder): AttackBuilder;\n onHit(count: number, die: RollBuilder): AttackBuilder;\n onHit(count: number, sides: number): AttackBuilder;\n onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;\n onHit(count: number, sides: number, modifier: number): AttackBuilder;\n onHit(...args: any[]): AttackBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new AttackBuilder(this, damageRoll);\n }\n\n get critThreshold(): number {\n return this.attackConfig.critThreshold;\n }\n\n // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?\n critOn(threshold: number): ACBuilder {\n const newConfig: AttackConfig = {\n ...this.attackConfig,\n critThreshold: threshold,\n };\n return new ACBuilder(this, this.attackConfig.ac, newConfig);\n }\n\n alwaysCrits(): AlwaysCritBuilder {\n return new AlwaysCritBuilder(\n this,\n {\n critThreshold: this.attackConfig.critThreshold,\n ac: this.attackConfig.ac,\n },\n false\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const configs = this.getSubRollConfigs(); // This already includes bonus dice, no need to add them again\n const expression = new RollBuilder(configs).toExpression();\n return this.attackConfig.ac\n ? `(${expression} AC ${this.attackConfig.ac})`\n : expression;\n }\n\n override toPMF(eps: number = 0): PMF {\n const ac = this.attackConfig.ac;\n\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n\n const staticMod = this.modifier;\n const bonusPMFs = this.getBonusDicePMFs(this, eps);\n\n // Build total to-hit value distribution attackRollPMF = d20 ⊕ bonusDice, then shift by staticMod\n const parts = [d20, ...bonusPMFs];\n let attackRollPMF = parts.length === 1 ? d20 : PMF.convolveMany(parts, eps);\n if (staticMod !== 0)\n attackRollPMF = attackRollPMF.mapDamage(\n (rollValue) => rollValue + staticMod\n );\n\n // Map to 0 when below AC\n const out = new Map();\n for (const rollValue of attackRollPMF.support()) {\n const p = attackRollPMF.pAt(rollValue);\n const key = rollValue >= ac ? rollValue : 0;\n out.set(key, (out.get(key) || 0) + p);\n }\n return PMF.fromMap(out, eps);\n }\n\n override copy(): ACBuilder {\n const baseCopy = new RollBuilder(this.getSubRollConfigs());\n const newConfig = {\n ac: this.attackConfig.ac,\n critThreshold: this.attackConfig.critThreshold,\n };\n return new ACBuilder(baseCopy, newConfig.ac, newConfig);\n }\n}\n\n// Augment the RollBuilder prototype to implement the ac method\nRollBuilder.prototype.ac = function (targetAC: number): ACBuilder {\n if (isNaN(targetAC)) throw new Error(\"Invalid NaN value for targetAC\");\n return new ACBuilder(this, targetAC);\n};\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { Mixture } from \"../pmf/mixture\";\nimport { PMF } from \"../pmf/pmf\";\nimport type { DiceQuery } from \"../pmf/query\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport type { DCBuilder } from \"./dc\";\nimport { ParsedRollBuilder, type RollBuilder } from \"./roll\";\nimport type { CheckBuilder, SaveResolution } from \"./types\";\n\nexport type SaveOutcome = \"normal\" | \"half\";\n\nexport class SaveBuilder implements CheckBuilder {\n constructor(\n readonly check: DCBuilder,\n private readonly failureEffect?: RollBuilder,\n private readonly saveOutcome: SaveOutcome = \"normal\"\n ) {}\n\n saveHalf(): SaveBuilder {\n return new SaveBuilder(this.check, this.failureEffect, \"half\");\n }\n\n toExpression(): string {\n const checkPart = this.check.toExpression();\n if (!this.failureEffect) return checkPart;\n\n const failureEffectPart = this.failureEffect.toExpression();\n const result = `${checkPart} * (${failureEffectPart})`;\n return this.saveOutcome === \"half\" ? `${result} save half` : result;\n }\n\n resolve(eps: number = EPS): SaveResolution {\n const { pSuccess: psuccess = 0, pFail: pfail = 1 } = resolveProbabilities(\n this.check\n );\n const failPMF = this.failureEffect\n ? this.failureEffect instanceof ParsedRollBuilder\n ? this.failureEffect.toPMF(eps)\n : pmfFromRollBuilder(this.failureEffect)\n : PMF.delta(0);\n const onSuccess = this.saveOutcome ?? \"half\";\n\n let successPMF: PMF = PMF.delta(0, eps);\n if (onSuccess === \"half\") successPMF = failPMF.scaleDamage(0.5, \"floor\");\n\n const successLabel: OutcomeType =\n onSuccess === \"normal\" ? \"missNone\" : \"saveHalf\";\n const failLabel: OutcomeType = \"saveFail\";\n const baseMix = new Mixture(eps);\n const mixture = baseMix\n .add(successLabel, successPMF, psuccess)\n .add(failLabel, failPMF, pfail);\n\n return {\n pmf: mixture.buildPMF(eps) ?? PMF.delta(0, eps),\n check:\n PMF.exclusive([[PMF.delta(1), psuccess]], eps) ?? PMF.delta(0, eps),\n saveFail: failPMF ?? PMF.delta(0, eps),\n saveSuccess: successPMF ?? PMF.delta(0, eps),\n weights: { success: psuccess, fail: pfail },\n };\n }\n\n // By default, create PMF with no pruning\n toPMF(eps: number = 0): PMF {\n return this.resolve(eps).pmf;\n }\n\n get pmf() {\n return this.toPMF();\n }\n\n // By default, create query on PMF with no pruning\n toQuery(eps: number = 0): DiceQuery {\n return this.toPMF(eps).query();\n }\n}\n\nfunction resolveProbabilities(check: DCBuilder): {\n pSuccess: number;\n pFail: number;\n} {\n const saveBonus = check.modifier;\n const dc = check.saveDC;\n const d20Type = check.rollType;\n const baseReroll = check.baseReroll;\n // TODO later check if base reroll is not 0 or 1.\n\n const die = d20RollPMF(d20Type, baseReroll > 0);\n const faceP = new Map();\n for (const [r, bin] of die) {\n const pr = bin.p;\n if (pr > 0) faceP.set(r, pr);\n }\n\n // Now add bonus dice to the PMF (bless, bane, bardic, etc)\n const eps = 0;\n const bonusDicePMFs = check.getBonusDicePMFs(check, eps);\n const bonusPMF =\n bonusDicePMFs.length > 0\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.zero(eps);\n\n let pSuccess = 0;\n for (let r = 1; r <= 20; r++) {\n const pr = faceP.get(r);\n if (!pr) continue;\n const need = dc - saveBonus - r;\n pSuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pFail = Math.max(0, 1 - pSuccess);\n return { pSuccess, pFail: pFail };\n}\n","import { PMF } from \"../pmf/pmf\";\nimport { pmfFromRollBuilder } from \"./ast\";\nimport { d20RollPMF } from \"./d20\";\nimport { RollBuilder } from \"./roll\";\nimport { SaveBuilder } from \"./save\";\n\ninterface SaveConfig {\n dc: number;\n}\n\nexport class DCBuilder extends RollBuilder {\n private readonly saveConfig: SaveConfig;\n\n constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig) {\n super(baseRoll.getSubRollConfigs());\n this.saveConfig = saveConfig ? { ...saveConfig } : { dc: 10 };\n }\n\n override dc(saveDC: number): DCBuilder {\n if (this.rollType && this.rollType === \"elven accuracy\") {\n throw new Error(\n \"Cannot use dc() on an AttackRollBuilder. Use ac() for attack rolls instead.\"\n );\n }\n return new DCBuilder(this, { dc: saveDC });\n }\n\n get saveDC(): number {\n return this.saveConfig.dc;\n }\n\n override add(anotherRoll: RollBuilder): DCBuilder {\n const newBuilder = super.add(anotherRoll);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n override addRoll(count?: number): DCBuilder {\n const newBuilder = super.addRoll(count);\n return new DCBuilder(newBuilder, this.saveConfig);\n }\n\n onSaveFailure(val: number): SaveBuilder;\n onSaveFailure(val: string): SaveBuilder;\n onSaveFailure(val: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder): SaveBuilder;\n onSaveFailure(count: number, sides: number): SaveBuilder;\n onSaveFailure(count: number, die: RollBuilder, modifier: number): SaveBuilder;\n onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder;\n onSaveFailure(...args: any[]): SaveBuilder {\n const damageRoll = RollBuilder.fromArgs(...args);\n return new SaveBuilder(this, damageRoll);\n }\n\n override withElvenAccuracy(): never {\n throw new Error(\n \"Elven Accuracy cannot be used with saving throws (DC checks). It is only valid for attack rolls (AC checks).\"\n );\n }\n\n // Legacy expressions\n override toExpression(): string {\n const subConfigs = this.getSubRollConfigs();\n const allConfigs = [...subConfigs];\n const expression = new RollBuilder(allConfigs).toExpression();\n return `(${expression} DC ${this.saveConfig.dc})`;\n }\n\n override toPMF(eps: number = 0): PMF {\n const saveDC = this.saveDC;\n const rollType = this.rollType;\n const rerollOne = this.baseReroll > 0;\n const d20 = d20RollPMF(rollType, rerollOne);\n const staticMod = this.modifier;\n const bonusDicePMFs = this.getBonusDiceConfigs().map((cfg) =>\n pmfFromRollBuilder(RollBuilder.fromConfigs([cfg]), eps)\n );\n const bonusPMF = bonusDicePMFs.length\n ? PMF.convolveMany(bonusDicePMFs, eps)\n : PMF.delta(0, eps);\n\n let psuccess = 0;\n for (const [r, bin] of d20) {\n const pr = bin.p;\n if (pr <= 0) continue;\n const need = saveDC - staticMod - r;\n psuccess += pr * bonusPMF.tailProbGE(need);\n }\n\n const pfail = Math.max(0, 1 - psuccess);\n const m = new Map([\n [0, psuccess > 0 ? psuccess : 0],\n [1, pfail > 0 ? pfail : 0],\n ]);\n return PMF.fromMap(m, eps);\n }\n}\n\n// Augment the RollBuilder prototype to implement the dc method\nRollBuilder.prototype.dc = function (saveDC: number): DCBuilder {\n if (isNaN(saveDC)) throw new Error(\"Invalid NaN value for saveDC\");\n return new DCBuilder(this).dc(saveDC);\n};\n"]} \ No newline at end of file diff --git a/dist/index.cjs b/dist/index.cjs deleted file mode 100644 index 372a2cf..0000000 --- a/dist/index.cjs +++ /dev/null @@ -1,3627 +0,0 @@ -'use strict'; - -// src/common/bounce.ts -function binom(n, k) { - if (k < 0 || k > n) return 0; - let result = 1; - for (let i = 0; i < k; i++) result = result * (n - i) / (i + 1); - return result; -} -function pAllDistinct(dice, faces, uniformCount, heavyWeight) { - const light = 1 / faces; - const eK = binom(uniformCount, dice) * Math.pow(light, dice) + heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1); - let kFactorial = 1; - for (let i = 2; i <= dice; i++) kFactorial *= i; - return kFactorial * eK; -} -function pMatch(dice, faces, minimumDieRoll) { - if (dice <= 1) return 0; - if (dice > faces) return 1; - if (minimumDieRoll >= 2) { - const uniformCount = faces - minimumDieRoll; - const effectiveValues = uniformCount + 1; - if (dice > effectiveValues) return 1; - const heavyWeight = minimumDieRoll / faces; - const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight); - return Math.min(1, Math.max(0, 1 - distinct)); - } - let pDistinct = 1; - for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces; - return 1 - pDistinct; -} -function calculateBounceOdds(diceCount, dieFaces, options) { - if (diceCount <= 1) return 0; - if (diceCount > dieFaces) return 1; - const minimumDieRoll = options?.minimumDieRoll ?? 0; - const rerollDamageDice = options?.rerollDamageDice ?? 0; - const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll); - const rerollCount = Math.min(rerollDamageDice, diceCount); - if (rerollCount <= 0) return pMatchFirst; - const pNoMatchFirst = 1 - pMatchFirst; - const keptDice = diceCount - rerollCount; - const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces; - const pRerollDieMissesAll = keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1; - const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll; - const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0; - const pMatchAfterReroll = Math.min( - 1, - pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches) - ); - return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll); -} - -// src/common/errors.ts -var DiceParseError = class _DiceParseError extends Error { - constructor(message, options) { - super(message); - this.name = "DiceParseError"; - this.expression = options?.expression; - this.cause = options?.cause; - Object.setPrototypeOf(this, _DiceParseError.prototype); - } -}; - -// src/common/lru-cache.ts -var LRUCache = class { - constructor(maxSize = 1e3) { - this.maxSize = maxSize; - this.cache = /* @__PURE__ */ new Map(); - } - get(key) { - const value = this.cache.get(key); - if (value === void 0) return void 0; - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - delete(key) { - this.cache.delete(key); - } - set(key, value) { - if (this.cache.size >= this.maxSize && !this.cache.has(key)) { - const oldestKey = this.cache.keys().next().value; - this.cache.delete(oldestKey); - } - this.cache.delete(key); - this.cache.set(key, value); - return this; - } - clear() { - this.cache.clear(); - } - get size() { - return this.cache.size; - } - has(key) { - return this.cache.has(key); - } - keys() { - return this.cache.keys(); - } - values() { - return this.cache.values(); - } -}; - -// src/common/types.ts -var EPS = 1e-12; -function critProbability(critRange, rollType = "flat") { - const base = critRange / 20; - switch (rollType) { - case "advantage": - return 1 - (1 - base) ** 2; - case "elven accuracy": - return 1 - (1 - base) ** 3; - case "disadvantage": - return base ** 2; - case "flat": - default: - return base; - } -} -var MISS_NONE_OUTCOME = "missNone"; -var ALL_OUTCOME_TYPES = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" -]; -var OUTCOME_DISPLAY_ORDER = [ - "crit", - "hit", - "missDamage", - "saveHalf", - "saveFail", - "pc", - "missNone" -]; -function sortOutcomes(outcomes, order = ALL_OUTCOME_TYPES) { - const rank = new Map(order.map((o, i) => [o, i])); - return [...outcomes].sort((a, b) => { - const ra = rank.get(a); - const rb = rank.get(b); - if (ra !== void 0 && rb !== void 0) return ra - rb; - if (ra !== void 0) return -1; - if (rb !== void 0) return 1; - return a.localeCompare(b); - }); -} -var onAnyHit = ["hit", "crit"]; -var onCritOnly = ["crit"]; -var onHitOnly = ["hit"]; -var onMissOnly = ["missNone", "missDamage"]; -var onMissDamageOnly = ["missDamage"]; -var onSaveHalfOnly = ["saveHalf"]; -var onSaveFailOnly = ["saveFail"]; -var onPotentCantripOnly = ["pc"]; - -// src/pmf/query.ts -var _DiceQuery = class _DiceQuery { - constructor(singles, combined, eps = EPS) { - this.singles = Array.isArray(singles) ? singles : [singles]; - if (this.singles.some((s) => s === void 0)) { - throw new Error("DiceQuery contains undefined singles"); - } - this._eps = eps; - this._combinedProvided = combined !== void 0; - if (combined !== void 0) { - this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); - } - } - /** - * The combined damage distribution of all single PMFs (their convolution), - * normalized to total probability 1. - * - * Computed lazily on first access and cached. Queries that only need - * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, - * {@link DiceQuery.stddev} — never trigger this convolution. - */ - get combined() { - if (this._combined === void 0) { - const c = PMF.convolveMany(this.singles); - this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); - } - return this._combined; - } - /** - * Returns a new PMF with damage attribution metadata populated. - * - * This method computes attribution on-demand for builder-generated PMFs, - * enabling them to work with damage attribution charts. The `attr` field - * tracks how much damage each outcome type contributes at each damage value. - * - * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) - * - * Performance: Cached after first call. Adds minimal overhead vs `combined`. - * - * @returns PMF with attr field populated for damage attribution charts - * - * @example - * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) - * const query = attack.toQuery() - * const pmf = query.combinedWithAttribution() - * // Now pmf can be used with toDamageAttributionChartSeries() - */ - combinedWithAttribution() { - if (this._combinedWithAttr) { - return this._combinedWithAttr; - } - if (this.singles.every((pmf) => pmf.hasAttribution())) { - this._combinedWithAttr = this.combined; - return this._combinedWithAttr; - } - const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); - const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); - const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); - this._combinedWithAttr = normalized; - return normalized; - } - /** - * Per-label `damage value → probability mass` series for the combined, - * attribution-carrying distribution — the provenance core of the stacked - * damage-attribution chart. Convenience for - * `combinedWithAttribution().attributionByValue()`; see - * {@link PMF.attributionByValue}. - */ - attributionByValue() { - return this.combinedWithAttribution().attributionByValue(); - } - /** - * How many of the independent single PMFs can produce the given outcome - * label. Useful for "all of them succeeded" style probabilities where the - * exponent is the number of contributing attacks (see - * {@link DiceQuery.probExactlyK}). - */ - countSinglesWith(label) { - let count = 0; - for (const single of this.singles) { - if (single.hasOutcome(label)) count++; - } - return count; - } - /** - * Returns the expected damage across all possible outcomes. - * - * Example: `query.mean()` → 12.5 - * Use case: "What's my average damage per round?" - */ - mean() { - if (this._combinedProvided) { - let m = 0; - for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; - return m; - } - let totalMean = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; - } - return totalMean; - } - /** - * Returns the variance of the damage distribution. - * - * Example: `query.variance()` → 45.2 - * Use case: "How much does my damage vary from the average?" - * High variance means higher risk/reward. Lower variance means more consistent damage. - */ - variance() { - if (this._combinedProvided) { - const mu = this.mean(); - let v = 0; - for (const [damageValue, bin] of this.combined) { - const dev = damageValue - mu; - v += dev * dev * bin.p; - } - return v; - } - let totalVariance = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - if (Math.abs(mass - 1) <= this._eps) { - totalVariance += single.variance(); - } else { - let mu = 0; - for (const [d, b] of single) mu += d * (b.p / mass); - let v = 0; - for (const [d, b] of single) { - const dev = d - mu; - v += dev * dev * (b.p / mass); - } - totalVariance += v; - } - } - return totalVariance; - } - /** - * Returns the standard deviation of the damage distribution. - * - * Example: `query.stdev()` → 6.7 - * Use case: "What's the typical spread around my average damage?" - * Used to determine how consistent the damage is. - */ - stddev() { - return Math.sqrt(this.variance()); - } - /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ - stdev() { - return this.stddev(); - } - /** - * Returns the Cumulative Distribution Function. - */ - cdf(x) { - return this.probTotalAtMost(x); - } - /** - * Returns the probability of dealing X damage or less. - * In statistics, this is called the cumulative distribution function (CDF). - * Example: `query.cdf(20)` → 0.75 - * Use case: "What's the chance I deal 20 damage or less?" - */ - probTotalAtMost(x) { - let cumulativeProbability = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue <= x) { - cumulativeProbability += probabilityBin.p; - } - } - return cumulativeProbability; - } - /** - * Returns the Complementary Cumulative Distribution Function. - */ - ccdf(x) { - return this.probTotalAtLeast(x); - } - /** - * Returns the probability of dealing at least X damage. - * - * Example: `query.probTotalAtLeast(25)` → 0.35 - * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" - */ - probTotalAtLeast(threshold) { - let probabilitySum = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue >= threshold) { - probabilitySum += probabilityBin.p; - } - } - return probabilitySum; - } - /** - * Returns damage values at specific percentiles. - * - * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] - * Use case: "What are my 25th, 50th, and 75th percentile damage values?" - */ - percentiles(percentileValues) { - const sortedDamageValues = this.combined.support(); - if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); - const cumulativeProbabilities = []; - let runningProbabilitySum = 0; - for (const damageValue of sortedDamageValues) { - runningProbabilitySum += this.combined.map.get(damageValue).p; - cumulativeProbabilities.push(runningProbabilitySum); - } - return percentileValues.map((targetPercentile) => { - let leftBound = 0; - let rightBound = cumulativeProbabilities.length - 1; - while (leftBound <= rightBound) { - const middleIndex = Math.floor((leftBound + rightBound) / 2); - if (cumulativeProbabilities[middleIndex] >= targetPercentile) { - rightBound = middleIndex - 1; - } else { - leftBound = middleIndex + 1; - } - } - return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; - }); - } - /** - * Returns the minimum possible damage. - * - * Example: `query.min()` → 0 - * Use case: "What's the worst-case damage if everything misses?" - */ - min() { - return this.combined.min(); - } - /** - * Returns the maximum possible damage. - * - * Example: `query.max()` → 56 - * Use case: "What's the best-case damage if everything crits and rolls max?" - */ - max() { - return this.combined.max(); - } - singleProb(diceIndex, label) { - const single = this.singles[diceIndex]; - let probabilitySum = 0; - for (const [, probabilityBin] of single) { - probabilitySum += probabilityBin.count[label] || 0; - } - const mass = single.mass(); - return mass > 0 ? probabilitySum / mass : 0; - } - /** - * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it - * carries ANY of `labels`", over the n independent singles. - * - * Each single's per-event success probability is the Poisson-binomial - * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), - * computed exactly once. The binomial DP then runs once to produce the whole - * distribution, so the array-label paths of probExactlyK / probAtLeastK / - * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and - * re-running the DP per requested k. - */ - countDistribution(labels) { - const n = this.singles.length; - const successProbabilities = this.singles.map( - (single) => new _DiceQuery([single]).probabilityOf(labels) - ); - const dist = new Array(n + 1).fill(0); - dist[0] = 1; - for (const successProb of successProbabilities) { - for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { - dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; - } - dist[0] *= 1 - successProb; - } - return dist; - } - probAtLeastK(labels, k) { - const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; - const n = this.singles.length; - if (k <= 0) return 1; - if (k > n) return 0; - const dist = this.countDistribution(L); - let tail = 0; - for (let i = k; i <= n; i++) { - tail += dist[i]; - } - if (tail < 0) return 0; - if (tail > 1) return 1; - return tail; - } - /** - * Returns the probability that at least one attack has the specified outcome(s). - * - This is the complement of probAtMostK(labels, 0) - * - * Examples: - * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) - * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) - * - * Use cases: - * - "What's the chance at least one of my attacks connects?" - * - * Note: - * - * - You have to pass in an array of labels to avoid double-counting if you are - * using multiple labels. You cannot just add them. - */ - probAtLeastOne(labels) { - if (typeof labels === "string") { - labels = [labels]; - } - let productOfNonOccurrence = 1; - for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { - let combinedProbability = 0; - for (const label of labels) { - combinedProbability += this.singleProb(diceIndex, label); - } - if (combinedProbability < 0) combinedProbability = 0; - else if (combinedProbability > 1) combinedProbability = 1; - productOfNonOccurrence *= 1 - combinedProbability; - } - const result = 1 - productOfNonOccurrence; - return result < 0 ? 0 : result > 1 ? 1 : result; - } - /** - * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. - * - * Uses dynamic programming to efficiently calculate the probability distribution - * of how many attacks will have the specified outcome, accounting for different - * success probabilities across individual attacks. - * - * Example: For 3 attacks with 50% hit chance each, returns: - * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] - * - * @param label - The outcome type to count - * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) - * @returns Array where index K contains P(exactly K attacks have the label) - */ - computeBinomialProbabilities(label, maxK) { - const individualProbabilities = this.singles.map( - (_, diceIndex) => this.singleProb(diceIndex, label) - ); - const binomialProbs = new Array(maxK + 1).fill(0); - binomialProbs[0] = 1; - for (const singleProbability of individualProbabilities) { - for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { - binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; - } - binomialProbs[0] *= 1 - singleProbability; - } - return binomialProbs; - } - /** - * Returns the probability that exactly K attacks result in the specified outcome(s). - * - * Single label examples: - * - probExactlyK('hit', 2) = probability exactly 2 attacks hit - * - probExactlyK('crit', 1) = probability exactly 1 attack crits - * - probExactlyK('crit', 0) = probability no attacks crit - * - * Array examples: - * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed - * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds - * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss - * - * Use cases: - * - "What's the chance exactly one of my attacks hits?" - * - "How likely am I to get exactly 2 successes out of 3 attacks?" - * - "What's the probability that exactly half my attacks succeed?" - * - * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. - * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. - */ - probExactlyK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - return probabilityArray[k]; - } - const dist = this.countDistribution(labels); - return k >= 0 && k < dist.length ? dist[k] : 0; - } - /** - * Returns the probability that AT MOST K attacks result in the specified outcome(s). - * - * Single label examples: - * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) - * - probAtMostK('crit', 0) = probability no attacks crit - * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss - * - * Array examples: - * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds - * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) - * - * Use cases: - * - "What's the chance that at most one attack hits?" (rest miss) - * - "How likely am I to have mostly failures?" (at most 1 success) - * - "What's the probability of a really bad turn?" (at most 0 successes) - * - */ - probAtMostK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - let cumulativeSum2 = 0; - for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { - cumulativeSum2 += probabilityArray[outcomeCount]; - } - return cumulativeSum2; - } - const dist = this.countDistribution(labels); - const upper = Math.min(k, dist.length - 1); - let cumulativeSum = 0; - for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { - cumulativeSum += dist[outcomeCount]; - } - return cumulativeSum; - } - /** - * Returns the expected damage attributed to specific outcome types. - * - * Single label examples: - * - expectedDamageFrom('hit') = expected damage from hit components - * - expectedDamageFrom('crit') = expected damage from crit components - * - * Array examples: - * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success - * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses - * - * Use cases: - * - "How much damage do I expect from successful attacks?" - * - "What's the damage contribution from critical hits specifically?" - * - "How much damage comes from miss effects (like save-for-half spells)?" - */ - expectedDamageFrom(labels) { - const wanted = Array.isArray(labels) ? labels : [labels]; - let total = 0; - for (const single of this.singles) { - for (const [dmg, bin] of single) { - let p = 0; - for (const label of wanted) p += bin.count[label] ?? 0; - total += dmg * p; - } - } - return total; - } - /** - * Returns damage statistics for scenarios where AT LEAST ONE attack results in - * the specified outcome(s). - * - * This method answers "What happens when things go reasonably well?" rather than - * "What's the theoretical maximum?" It includes mixed scenarios which are more - * common and tactically relevant than pure scenarios. - * - * Single label examples: - * - damageStatsFrom('hit') = damage range when at least one attack hits - * - damageStatsFrom('crit') = damage range when at least one attack crits - * - * Array examples: - * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds - * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses - * - * Tactical Use Cases: - * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" - * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" - * - "Should I use this risky spell if it has good damage when it works?" - * - "What's my damage potential when something goes right?" (vs pure failure) - * - * Combat Planning Examples: - * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" - * (Much more useful than "You average 50 damage including complete misses") - * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" - * - Resource management: "If I hit anything, I'll likely finish this enemy" - * - * Statistical Note: - * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which - * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. - * - * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an - * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and - * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than - * E[dmg | the label occurs]. For a single attack both are the plain - * conditional figures. Use {@link probAtLeastOne} for the scenario probability. - * - * @example - * // High-level tactical planning - * const successStats = query.damageStatsFrom('hit') - * const successChance = query.probAtLeastOne('hit') - * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) - */ - damageStatsFrom(labels) { - const labelArray = typeof labels === "string" ? [labels] : labels; - let minDamage = Infinity; - let maxDamage = -Infinity; - let totalDamage = 0; - let totalCount = 0; - for (const [damage, probabilityBin] of this.combined) { - let binHasAnyLabel = false; - let binContribution = 0; - for (const label of labelArray) { - const count = probabilityBin.count[label]; - if (count && count > 0) { - binHasAnyLabel = true; - binContribution += count; - } - } - if (damage > 0 && binHasAnyLabel) { - minDamage = Math.min(minDamage, damage); - maxDamage = Math.max(maxDamage, damage); - const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; - totalDamage += damage * weightToUse; - totalCount += weightToUse; - } - } - return { - min: minDamage === Infinity ? 0 : minDamage, - max: maxDamage === -Infinity ? 0 : maxDamage, - avg: totalCount > 0 ? totalDamage / totalCount : 0, - count: totalCount - }; - } - /** - * Returns damage statistics for scenarios where ALL attacks result in the specified - * outcome, calculated by leveraging the pure partition of singles. - * - * This method answers "What's the theoretical best/worst case?" and "What are the - * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. - * - * Examples: - * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) - * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) - * - * UI and Display Use Cases: - * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) - * - "Best case scenario" vs "worst case scenario" analysis - * - Mathematical verification: "Does our hit damage calculation match manual math?" - * - Clean damage type attribution: "How much comes from base hits vs crits?" - * - * Design and Balance Use Cases: - * - Game designers: "What's the damage ceiling if someone gets lucky?" - * - Character optimization: "What's my absolute maximum potential?" - * - Ability comparison: "Which build has higher crit ceiling?" - * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" - * - * Mathematical Use Cases: - * - Validating complex calculations against simple manual math - * - Understanding damage component contributions in isolation - * - Separating luck (crit variance) from consistency (hit variance) - * - Building intuition about damage sources - * - * When to Use This vs damageStatsFrom(): - * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons - * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios - * - * Statistical Note: - * Pure scenarios (all hits, all crits) are rare but represent clear mathematical - * boundaries. These stats help understand the "shape" of your damage potential. - * - * @example - * // UI display logic - * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" - * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" - * - * // vs tactical planning (use damageStatsFrom instead) - * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios - */ - combinedDamageStats(targetLabel) { - const singleStats = this.singles.map( - (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) - ); - if (singleStats.some((stats) => stats.count === 0)) { - return { min: 0, max: 0, avg: 0, count: 0 }; - } - const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); - const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); - const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); - const combinedProb = singleStats.reduce( - (product, stats) => product * stats.count, - 1 - ); - return { - min: combinedMin, - max: combinedMax, - avg: combinedAvg, - count: combinedProb - }; - } - /** - * Returns the probability that at least one attack carries ANY of the - * specified labels (the marginal P(≥1) across the independent attacks). - * - * Examples: - * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) - * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) - * - * Use cases: - * - "What's the chance my resolution includes a success label?" - * - "How likely am I to get any hits or crits across all attacks?" - * - * Note: this must NOT be computed by summing `combined` bin probabilities. A - * single combined damage total is reachable by many outcome combinations and - * a bin can hold several labels at once, so summing `bin.p` over bins that - * contain a label over-counts. The correct marginal is the Poisson-binomial - * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. - */ - probabilityOf(labels) { - return this.probAtLeastOne(labels); - } - /** - * Returns the probability of missing (any type of miss). - * - * Example: `query.missChance()` → 0.04 - * Use case: "What's the chance I miss completely this turn?" - */ - missChance() { - return this.probabilityOf(["missDamage", "missNone"]); - } - /** - * Returns data formatted for plotting damage probability distribution. - * - * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] - * Use case: "I want to visualize my damage distribution in a chart." - */ - toChartSeries() { - return this.combined.support().map((damageValue) => ({ - x: damageValue, - y: this.combined.map.get(damageValue).p - })); - } - /** - * Returns tabular data showing damage values and their probability breakdowns. - * - * Example: `query.toLabeledTable(['hit', 'crit'])` → - * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] - * - * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." - */ - toLabeledTable(labels = []) { - return this.combined.support().map((damageValue) => { - const probabilityBin = this.combined.map.get(damageValue); - const tableRow = { - damage: damageValue, - total: probabilityBin.p - }; - for (const outcomeLabel of labels) { - tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; - } - return tableRow; - }); - } - /** - * Returns data for stacked charts with unconditional per-label probability mass per damage. - * - * - Each dataset value equals the unconditional probability mass for that label at that damage - * (i.e., `bin.count[label]`). - * - Column sums may be less than the total probability `bin.p` when you omit labels or when - * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. - * - This behavior matches tests that expect raw per-label mass (not proportional scaling). - * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. - * - * @example - * query.toStackedChartData(['hit', 'crit']) - * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} - */ - toStackedChartData(labels = [], epsilon = EPS) { - const damageValues = this.combined.support(); - const datasets = labels.map((outcomeLabel) => ({ - label: outcomeLabel, - data: damageValues.map((dmg) => { - const bin = this.combined.map.get(dmg); - const v = bin ? bin.count[outcomeLabel] || 0 : 0; - return v <= epsilon ? 0 : v; - }) - })); - return { labels: damageValues, datasets }; - } - /** - * Returns pure mathematical data for attribution charts showing outcome contributions. - * - * Automatically discovers all outcome types present in the PMF, applies filtering rules, - * and returns proportional data suitable for stacked visualization. - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and proportional data - * - * @example - * query.toAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} - */ - toAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeCount = bin.count[outcome] || 0; - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - const outcomeProbability = bin.p * outcomeFraction; - return asPercentages ? outcomeProbability * 100 : outcomeProbability; - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for damage attribution charts showing damage contribution - * from each outcome type at each damage value. - * - * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of - * bin.count (probability attribution). - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) - * @returns Pure data structure with support, outcomes, and damage attribution data - * - * @example - * query.toDamageAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} - */ - toDamageAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - if (bin.attr) { - for (const outcomeType in bin.attr) { - if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin || !bin.attr) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeDamageAttribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { - if (filterRules(outcomeName, damage)) { - totalDamageAttribution += damageAttr || 0; - } - } - if (totalDamageAttribution === 0) return 0; - const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; - return damagePercentage * bin.p * 100; - } else { - return outcomeDamageAttribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for outcome attribution charts showing which - * attack outcome combinations can produce each damage value. - * - * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks - * outcome combinations - answering "what attack outcomes produced this damage?" - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and outcome combination probabilities - * - * @example - * query.toOutcomeAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} - */ - toOutcomeAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - if (outcome === "missNone") { - const outcomeCount = bin.count[outcome] || 0; - if (outcomeCount === 0) return 0; - if (asPercentages) { - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - return outcomeFraction * bin.p * 100; - } else { - return outcomeCount; - } - } - if (!bin.attr) return 0; - const outcomeDamageContribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [, damageAttr] of Object.entries(bin.attr)) { - totalDamageAttribution += damageAttr || 0; - } - if (totalDamageAttribution === 0) return 0; - const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; - return outcomeFraction * bin.p * 100; - } else { - return outcomeDamageContribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for cumulative distribution function (CDF). - * Shows P(X ≤ x) - the probability of getting at most x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and cumulative probabilities - * - * @example - * query.toCDFSeries() - * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} - */ - toCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const cdfData = []; - for (const damage of support) { - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - cdfData.push( - asPercentages ? cumulativeProbability * 100 : cumulativeProbability - ); - } - return { - support, - data: cdfData - }; - } - /** - * Returns pure mathematical data for complementary cumulative distribution function (CCDF). - * Shows P(X ≥ x) - the probability of getting at least x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and complementary cumulative probabilities - * - * @example - * query.toCCDFSeries() - * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} - */ - toCCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const ccdfData = []; - for (const damage of support) { - const ccdf = 1 - cumulativeProbability; - ccdfData.push(asPercentages ? ccdf * 100 : ccdf); - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - } - return { - support, - data: ccdfData - }; - } - /* - Statistics snapshot of the query. - */ - /** Probability of doing strictly more than threshold damage (default >0). */ - probDamageGreaterThan(threshold = 0) { - let acc = 0; - for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; - return acc; - } - /** All outcome keys actually present (typed & ordered if you pass an order). */ - outcomeKeys(order) { - const found = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) - if (bin.count[k] && bin.count[k] > 0) found.add(k); - } - if (found.size === 0) - ["hit", "crit", "missNone"].forEach((k) => found.add(k)); - const keys = Array.from(found).filter( - (k) => order?.includes(k) ?? true - ); - if (order && order.length) - keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); - return keys; - } - /** Total probability per outcome across the PMF. */ - outcomeTotals(outcomes = this.outcomeKeys()) { - const totals = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => totals.set(o, 0)); - for (const [, row] of this.combined.map) { - for (const o of outcomes) { - const p = row.count[o] || 0; - totals.set(o, (totals.get(o) || 0) + p); - } - } - return totals; - } - /** Conditional damage range per outcome (min/avg/max of X | outcome). */ - outcomeDamageRanges(outcomes = this.outcomeKeys()) { - const table = this.toLabeledTable(outcomes); - const ranges = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); - for (const row of table) { - const dmg = row.damage; - for (const o of outcomes) { - const p = row[o] || 0; - if (p > 0) { - const r = ranges.get(o); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - } - const out = /* @__PURE__ */ new Map(); - for (const o of outcomes) { - const r = ranges.get(o); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); - } - return out; - } - /** - * Snapshot of the distribution in the exact shape the UI consumes. - * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) - * - damageRange is conditional on the outcome occurring - * - * The outcome probabilities use the correct Poisson-binomial marginals - * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), - * so they are always valid probabilities in [0,1]. - * - * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from - * the combined PMF's `count`, which the convolution accumulates as an EXPECTED - * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] - * rather than a clean conditional expectation. It is correct for a single - * attack. - */ - snapshot(order) { - const discovered = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) { - if (bin.count[k] && bin.count[k] > 0) discovered.add(k); - } - } - if (discovered.size === 0) { - for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); - } - let outcomes = Array.from(discovered); - if (order && order.length) { - const inOrder = new Set(order); - outcomes = outcomes.filter((k) => inOrder.has(k)); - const rank = new Map(order.map((k, i) => [k, i])); - outcomes.sort( - (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) - ); - } - const rows = this.toLabeledTable(outcomes); - const rangeAcc = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - rangeAcc.set(ot, { sum: 0, mass: 0 }); - } - for (const row of rows) { - const dmg = row.damage; - for (const ot of outcomes) { - const p = row[ot] || 0; - if (p <= 0) continue; - const r = rangeAcc.get(ot); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - const n = this.singles.length; - const outcomeMap = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - const r = rangeAcc.get(ot); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - outcomeMap.set(ot, { - atLeastOneProbability: this.probAtLeastOne(ot), - allProbability: this.probAtLeastK(ot, n), - damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } - }); - } - const averageDPR = this.mean(); - let damageChance = 0; - for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; - const { support, data } = this.toCDFSeries(false); - const quantile = (p) => { - if (support.length === 0) return 0; - for (let i = 0; i < support.length; i++) - if (data[i] >= p) return support[i]; - return support[support.length - 1]; - }; - const percentiles = { - p25: quantile(0.25), - p50: quantile(0.5), - p75: quantile(0.75) - }; - return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; - } - /** - * PMF Transformation Methods - * - * These methods provide a fluent API for transforming dice queries by wrapping - * the underlying PMF transformation methods. All operations work on the combined - * PMF and return new DiceQuery instances. - */ - /** - * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). - * - * @returns New DiceQuery with normalized combined PMF - */ - normalize() { - return new _DiceQuery([this.combined.normalize()]); - } - /** - * Returns a new DiceQuery with low-probability outcomes removed. - * - * @param eps Minimum probability threshold (defaults to PMF epsilon) - * @param keepFinalBin Whether to keep the highest damage bin regardless of probability - * @returns New DiceQuery with compacted combined PMF - */ - compact(eps, keepFinalBin) { - return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); - } - /** - * Returns a new DiceQuery with an additional scaled branch added. - * Useful for conditional outcomes like "30% chance of opportunity attack". - * - * @param branch DiceQuery to add as a scaled branch - * @param probability Probability of the branch occurring (0-1) - * @returns New DiceQuery combining this query with the scaled branch - * - * @example - * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); - * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); - */ - addScaled(branch, probability) { - return new _DiceQuery([ - this.combined.addScaled(branch.combined, probability) - ]); - } - /** - * Returns a new DiceQuery with all probabilities scaled by a factor. - * Used for conditional scenarios where the entire outcome has reduced probability. - * - * @param factor Scaling factor for probabilities - * @returns New DiceQuery with scaled probabilities - * - * @example - * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario - */ - scaleMass(factor) { - return new _DiceQuery([this.combined.scaleMass(factor)]); - } - totalMass() { - return this.combined.mass(); - } - /** - * Returns a new DiceQuery with damage values transformed by a function. - * Useful for applying modifiers, resistances, or other damage transformations. - * - * @param damageTransformFunction Function to transform each damage value - * @returns New DiceQuery with transformed damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage - * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage - */ - mapDamage(damageTransformFunction) { - return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); - } - /** - * Returns a new DiceQuery with damage values scaled by a factor. - * Convenient wrapper around mapDamage for multiplicative scaling. - * - * @param factor Scaling factor for damage values - * @param rounding Rounding method: "floor" (default), "round", or "ceil" - * @returns New DiceQuery with scaled damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const doubled = baseAttack.scaleDamage(2); // Double damage - * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded - */ - scaleDamage(factor, rounding = "floor") { - return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); - } - /** - * Returns a new DiceQuery combining this query with another via convolution. - * Equivalent to rolling both queries independently and adding results. - * It is important to use this rather than combing()ing the PMFs directly! - * This method maintains the provenance of the PMFs which is needed for damage attribution. - * Combining the .combined PMFs directly is still valid for DPR calculations but - * is not statistically sound for queries. - * - * @param other DiceQuery to combine with - * @param eps Optional epsilon for precision control - * @returns New DiceQuery representing the combined outcome - * - * @example - * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); - * const bothAttacks = mainAttack.convolve(bonusAttack); - */ - convolve(other) { - const singles = [...this.singles, ...other.singles]; - return new _DiceQuery(singles); - } - /** - * First-success split over an ordered list of DISTINCT single-swing PMFs. - * Each PMF may have different success/subset probabilities (from labels). - * - * successOutcome: e.g., ["success"] or ["hit", "crit"] - * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success - * - * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] - */ - firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { - const pmfs = this.singles; - if (!pmfs.length) { - throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); - } - const toArr = (x) => Array.isArray(x) ? x : [x]; - const clamp01 = (x) => Math.max(0, Math.min(1, x)); - const tol = Math.max(eps, 8 * Number.EPSILON); - const per = pmfs.map((pmf) => { - const dq = new _DiceQuery([pmf]); - const pS = dq.probAtLeastOne(toArr(successOutcome)); - const pB = dq.probAtLeastOne(toArr(subsetOutcome)); - if (pB - pS > eps) { - throw new Error( - "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." - ); - } - return { pS, pB }; - }); - let missSoFar = 1; - let pFirstSubset = 0; - let pFirstNonSubset = 0; - let pNone = 1; - for (const { pS, pB } of per) { - pFirstSubset += missSoFar * pB; - pFirstNonSubset += missSoFar * (pS - pB); - const miss = 1 - pS; - missSoFar *= miss; - pNone *= miss; - } - const pAny = 1 - pNone; - const a = clamp01(pFirstNonSubset); - const b = clamp01(pFirstSubset); - const any = clamp01(pAny); - const none = clamp01(pNone); - if (Math.abs(a + b - any) > tol * Math.max(1, any)) { - throw new Error( - `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` - ); - } - return [a, b, any, none]; - } -}; -_DiceQuery.DEFAULT_OUTCOMES = [ - "hit", - "crit", - "missNone" -]; -var DiceQuery = _DiceQuery; -var pmfCache = new LRUCache(1e3); -var _PMF = class _PMF { - constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { - this.map = map; - this.epsilon = epsilon; - this.normalized = normalized; - this.identifier = identifier; - this._preservedProvenance = _preservedProvenance; - } - static empty(epsilon = EPS, identifier = "empty") { - return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); - } - // This has a single bin at value 0, mass of 1 - static zero(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "zero"); - } - static delta(value, epsilon = EPS) { - return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); - } - /** - * Point mass at damage 0 tagged with the canonical `missNone` outcome. - * - * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the - * builder's attack-resolution vocabulary. This uses the `missNone` - * {@link OutcomeType} that the attribution charts and outcome stats key on, - * so it is the correct "clean miss / no damage" delta for provenance-aware - * mixtures feeding those consumers. - */ - static missNone(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "missNone"); - } - // This creates a single bin at value 0, but with weight 0. - static emptyMass() { - return _PMF.zero().scaleMass(0); - } - // Makes PMF iterable over [damage, bin] pairs. - [Symbol.iterator]() { - return this.map[Symbol.iterator](); - } - static clearCache() { - pmfCache.clear(); - } - /** - * Creates a conditional PMF from two branches (success and failure) and a probability. - * This is the core logic for modeling any probabilistic event where there are two - * distinct outcomes. - */ - static branch(successPMF, failurePMF, successProbability) { - let p = successProbability; - if (!Number.isFinite(p)) p = 0; - if (p < 0) p = 0; - if (p > 1) p = 1; - const q = 1 - p; - if (p === 0) return failurePMF.scaleMass(1); - if (p === 1) return successPMF.scaleMass(1); - const eps = successPMF.epsilon ?? failurePMF.epsilon; - const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; - const resultMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of failurePMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); - } - for (const [damageValue, bin] of successPMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); - } - return new _PMF(resultMap, eps, false, id); - } - /** - * withProbability() - * - * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). - * - * Think of this as a shortcut for: - * pmf.gate(p, PMF.zero()) - * - * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, - * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. - * - * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. - * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. - * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) - * use query.firstSuccessSplit() to get the exact probabilities. - * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). - * - */ - static withProbability(successPMF, probability) { - return _PMF.branch(successPMF, _PMF.zero(), probability); - } - /** - * gate() - * - * A conditional wrapper around branch() that applies this PMF with probability `p`, - * and applies a provided fallback PMF otherwise. - * - * This is useful for modeling a binary choice between two outcomes: - * - The "success" outcome (this PMF) happens with probability `p`. - * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. - * - * Examples: - * - 25% chance to include an opportunity attack, otherwise nothing: - * attackPMF.gate(0.25, PMF.zero()) - * - * - 50% chance to deal fireball damage, otherwise cone of cold damage: - * fireballPMF.gate(0.5, coneOfColdPMF) - * - * Relationship to other helpers: - * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. - * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). - * - * @param p Probability of applying this PMF (between 0 and 1). - * @param fallback PMF to apply when this PMF is *not* selected. - * @returns A new PMF representing the weighted mixture of this PMF and the fallback. - */ - gate(p, fallback) { - return _PMF.branch(this, fallback, p); - } - /** - * PMF.exclusive() - * - * Builds a single PMF from a set of mutually exclusive weighted outcomes. - * Exactly one of the provided options will occur. - * - * Each option has: - * - A PMF representing its outcome (e.g., damage dice). - * - A weight representing its probability of being selected. - * - * Notes: - * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for floating point rounding. - */ - static exclusive(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight) || weight < -eps) { - throw new Error(`PMF.exclusive: invalid weight ${weight}.`); - } - } - let totalWeight = items.reduce((s, { weight }) => s + weight, 0); - if (Math.abs(totalWeight) <= eps) totalWeight = 0; - if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; - if (totalWeight > 1 + EPS) { - throw new Error( - `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` - ); - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (weight > eps) out = out.addScaled(pmf, weight); - } - const leftover = Math.max(0, 1 - totalWeight); - if (leftover > eps) { - out = out.addScaled(_PMF.zero(), leftover); - } - return out; - } - /** - * PMF.mix() - * - * Builds a PMF as a linear combination of input PMFs with the given weights. - * Unlike `exclusive`, this does NOT: - * - enforce that weights sum to 1 - * - add leftover probability to δ0 (PMF.zero()) - * - * Use when outcomes are not mutually exclusive, or for interpolation/blending. - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for skipping tiny weights. - */ - static mix(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight)) { - throw new Error(`PMF.mix: invalid weight ${weight}.`); - } - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (Math.abs(weight) <= eps) continue; - out = out.addScaled(pmf, weight); - } - return out; - } - /** - * Adds damage attribution metadata to this PMF based on existing count metadata. - * For each bin, sets attr[outcome] = damage × count[outcome]. - * - * This enables damage attribution charts to work with builder-generated PMFs. - * The parser generates attr automatically, but builder PMFs only have count. - * - * @returns New PMF with attr field populated in each bin - */ - /** - * Returns true if this PMF already carries damage attribution metadata. - * - * Only the first positive-damage bin is inspected (parser-generated PMFs - * populate `attr` uniformly), so this is O(1) in practice. - */ - hasAttribution() { - for (const [damage, bin] of this.map) { - if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { - return true; - } - if (damage > 0) break; - } - return false; - } - withAttribution() { - if (this.hasAttribution()) return this; - const newMap = /* @__PURE__ */ new Map(); - for (const [damage, bin] of this.map) { - const attr = {}; - for (const outcome in bin.count) { - const probability = bin.count[outcome]; - if (probability > 0) { - attr[outcome] = damage * probability; - } - } - newMap.set(damage, { - p: bin.p, - count: { ...bin.count }, - attr: Object.keys(attr).length > 0 ? attr : void 0 - }); - } - return new _PMF( - newMap, - this.epsilon, - this.normalized, - `${this.identifier}~attr` - ); - } - /** - * General-purpose N-way mixture. - * weights: Array of [weight, PMF]. - * - * Example: PMF.mixN([ - * [pMiss, zero], - * [pHit, hitPMF], - * [pCrit, critPMF], - * ]); - */ - static mixN(weights, eps = EPS) { - const filtered = weights.filter(([w]) => w > eps); - if (filtered.length === 0) { - return _PMF.emptyMass(); - } - let acc = null; - let sum = 0; - for (const [w, pmf] of filtered) { - if (acc === null) { - acc = pmf; - sum = w; - } else { - const q = w / (sum + w); - acc = _PMF.branch(pmf, acc, q); - sum += w; - } - } - return acc ?? _PMF.emptyMass(); - } - // This is a convenience method for when we use power - // TODO: It can be smarter in the future, and we can also add it to query - // That way statistics operations on invalid PMFs can throw an error - // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? - setPreservedProvenance(preserved) { - if (!this._preservedProvenance && preserved) { - throw new Error( - "Preserved provenance is already set to false, cannot fix that" - ); - } - this._preservedProvenance = preserved; - } - preservedProvenance() { - return this._preservedProvenance; - } - getPowerCacheKey(n, eps) { - const id = this.identifier; - let key = `${id}`; - for (let i = 1; i < n; i++) key += `+${id}`; - return `${key}@${eps}`; - } - /** - * Efficiently computes this PMF convolved with itself `n` times. - * Uses exponentiation by squaring to reduce total convolutions. - * n must be a positive integer. - * * - * * NOTE: This folds multiple independent attacks into a single PMF. - * As a result, The power() method causes a loss of data provenance. - * This is ONLY SAFE if you are trying to calculate masses. - * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). - */ - power(n, eps = this.epsilon) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("power(n): n must be a positive integer"); - } - if (n === 1) return this; - const epsilon = eps ?? this.epsilon; - const key = this.getPowerCacheKey(n, epsilon); - { - const cached = pmfCache?.get(key); - if (cached) return cached; - } - let base = this.normalized ? this : this.normalize(); - let result = base; - let exp = n - 1; - while (exp > 0) { - if (exp & 1) { - result = result.convolve(base, epsilon); - } - exp >>= 1; - if (exp > 0) { - base = base.convolve(base, epsilon); - } - } - result.setPreservedProvenance(false); - { - pmfCache?.set(key, result); - } - return result; - } - /* - * Helper for chaining multiple identical attacks - */ - replicate(n) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("replicate(n): n must be a positive integer"); - } - if (n === 1) return [this]; - return Array.from({ length: n }, () => this); - } - mass() { - if (this._totalMass === void 0) { - let totalProbabilityMass = 0; - for (const { p } of this.map.values()) { - totalProbabilityMass += p; - } - this._totalMass = totalProbabilityMass; - } - return this._totalMass; - } - outcomeMass(outcome) { - let totalProbabilityMass = 0; - for (const { p, count } of this.map.values()) { - totalProbabilityMass += p * (count[outcome] ?? 0); - } - return totalProbabilityMass; - } - // Helper for testing - faceTotal() { - return [...this.map.keys()].reduce((sum, key) => sum + key, 0); - } - normalize() { - if (this.normalized) return this; - const normalizationFactor = this.mass(); - if (normalizationFactor === 0) return this; - const normalizedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const normalizedCount = {}; - for (const labelKey in probabilityBin.count) { - normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; - } - let normalizedAttributes; - if (probabilityBin.attr) { - normalizedAttributes = {}; - for (const labelKey in probabilityBin.attr) { - normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; - } - } - normalizedMap.set(damageValue, { - p: probabilityBin.p / normalizationFactor, - count: normalizedCount, - attr: normalizedAttributes - }); - } - return new _PMF(normalizedMap, this.epsilon, true, this.identifier); - } - /** - * Returns a copy with negligible probabilities removed (p < eps). - * If keepFinalBin is true, the bin with the largest key is always kept, - * even if its probability is below eps. count/attr submaps are still cleaned. - */ - compact(eps = this.epsilon, keepFinalBin = false) { - let maxKey = -Infinity; - if (keepFinalBin) { - for (const key of this.map.keys()) { - if (key > maxKey) maxKey = key; - } - } - const compactedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; - if (!shouldKeep) continue; - const cleanedBin = _PMF.cloneBin(probabilityBin); - for (const labelKey in cleanedBin.count) { - if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { - delete cleanedBin.count[labelKey]; - } - } - if (cleanedBin.attr) { - for (const labelKey in cleanedBin.attr) { - if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { - delete cleanedBin.attr[labelKey]; - } - } - if (Object.keys(cleanedBin.attr).length === 0) { - cleanedBin.attr = void 0; - } - } - compactedMap.set(damageValue, cleanedBin); - } - return new _PMF(compactedMap, eps, this.normalized, this.identifier); - } - // Note: The "support" of a PMF is the set of all non-zero probability outcomes. - // This returns all damage values with non-zero probability, sorted ascending. - support() { - if (this._support === void 0) { - this._support = [...this.map.keys()].sort((a, b) => a - b); - } - return this._support; - } - // Minimum possible damage value. - min() { - if (this._min === void 0) { - const support = this.support(); - this._min = support.length > 0 ? support[0] : 0; - } - return this._min; - } - // Maximum possible damage value. - max() { - if (this._max === void 0) { - const support = this.support(); - this._max = support.length > 0 ? support[support.length - 1] : 0; - } - return this._max; - } - /** - * Returns the expected (mean) damage value. - * Cached for performance since this requires iterating through all bins. - */ - mean() { - if (this._mean === void 0) { - let totalSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - totalSum += damageValue * probabilityBin.p; - } - this._mean = totalSum; - } - return this._mean; - } - /** - * Returns the variance of the damage distribution. - * Cached for performance since this requires mean calculation plus iteration. - */ - variance() { - if (this._variance === void 0) { - const meanValue = this.mean(); - let varianceSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - const deviationFromMean = damageValue - meanValue; - varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; - } - this._variance = varianceSum; - } - return this._variance; - } - /** - * Returns the standard deviation of the damage distribution. - */ - stdev() { - if (this._stdev === void 0) { - this._stdev = Math.sqrt(this.variance()); - } - return this._stdev; - } - /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ - static cloneBin(bin) { - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ - static scaleBin(bin, factor) { - const count = {}; - for (const k in bin.count) { - count[k] = bin.count[k] * factor; - } - let attr; - if (bin.attr) { - attr = {}; - for (const k in bin.attr) { - attr[k] = bin.attr[k] * factor; - } - } - return { p: bin.p * factor, count, attr }; - } - static mergeInto(destinationMap, damageValue, binToAdd) { - const existingBin = destinationMap.get(damageValue); - if (!existingBin) { - destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); - return; - } - existingBin.p += binToAdd.p; - for (const labelKey in binToAdd.count) { - existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; - } - if (binToAdd.attr) { - if (!existingBin.attr) { - existingBin.attr = {}; - } - for (const labelKey in binToAdd.attr) { - existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; - } - } - } - // Convenience method - add(other) { - return this.addScaled(other, 1); - } - /** - * Returns a new PMF with a scaled branch added to this one. - * The branch PMF is scaled by the given probability before merging - * This will be very useful for conditional effects and for being - * able to model "I can probably have this opportunity attack 40% of rounds" - * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes - */ - addScaled(branch, probability) { - if (probability === 0) return this; - const resultMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of this.map) { - resultMap.set(dmg, _PMF.cloneBin(bin)); - } - for (const [damageValue, probabilityBin] of branch.map) { - _PMF.mergeInto( - resultMap, - damageValue, - _PMF.scaleBin(probabilityBin, probability) - ); - } - return new _PMF( - resultMap, - this.epsilon, - false, - `${this.identifier}+scaled(${branch.identifier},${probability})` - ); - } - /** - * Redistributes probability mass to model an effect that only occurs with - * probability `frequency` — a conditional attack, an on-hit rider, or a - * sub-one AoE target fraction. - * - * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, - * per-label `count`, AND per-label `attr` — and the freed mass is moved into - * the miss bin at damage 0, tagged with the canonical `missNone` outcome. - * Total probability mass is preserved. - * - * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage - * attribution (`attr`) intact, so a frequency-scaled PMF still renders - * correctly in the damage-attribution charts. - * - * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` - * collapses all mass into the miss bin. The miss outcome is assumed to be - * encoded at damage value 0. - * - * @param frequency Probability in [0, 1] that the effect occurs. - */ - applyHitFrequency(frequency) { - if (!Number.isFinite(frequency) || frequency >= 1) return this; - const freq = Math.max(0, frequency); - const pMiss = this.pAt(0); - const pHit = 1 - pMiss; - const newMissMass = pMiss + (1 - freq) * pHit; - const newMap = /* @__PURE__ */ new Map(); - newMap.set(0, { - p: newMissMass, - count: { [MISS_NONE_OUTCOME]: newMissMass }, - attr: {} - }); - for (const [damage, bin] of this.map) { - if (damage <= 0) continue; - newMap.set(damage, _PMF.scaleBin(bin, freq)); - } - return new _PMF( - newMap, - this.epsilon, - false, - `freq(${this.identifier},${freq})` - ); - } - scaleMass(factor) { - if (factor === 1) return this; - const scaledMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); - } - return new _PMF( - scaledMap, - this.epsilon, - false, - `scale(${this.identifier},${factor})` - ); - } - mapDamage(damageTransformFunction) { - const transformedMap = /* @__PURE__ */ new Map(); - for (const [originalDamage, probabilityBin] of this.map) { - const transformedDamage = damageTransformFunction(originalDamage); - _PMF.mergeInto( - transformedMap, - transformedDamage, - _PMF.cloneBin(probabilityBin) - ); - } - return new _PMF( - transformedMap, - this.epsilon, - this.normalized, - `map(${this.identifier})` - ); - } - scaleDamage(factor, rounding = "floor") { - const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; - return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); - } - getPMFCombineCacheKey(p1, p2, eps, raw) { - const [id1, id2] = [p1.identifier, p2.identifier].sort(); - return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; - } - /** - * A small content fingerprint (mass + bin count + face sum) so convolution - * cache keys change if the underlying numbers do. Memoized because a PMF is - * immutable once constructed — this avoids re-summing every key on each - * convolve() call (including cache hits). - */ - fingerprint() { - if (this._fingerprint === void 0) { - let faceSum = 0; - for (const k of this.map.keys()) faceSum += k; - this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; - } - return this._fingerprint; - } - convolve(other, eps, raw = false) { - const epsilon = eps ?? this.epsilon; - const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); - const A0 = norm(this); - const B0 = norm(other); - const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; - const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); - const cached = pmfCache?.get(cacheKey); - if (cached) return cached; - const combinedMap = /* @__PURE__ */ new Map(); - for (const [aVal, aBin] of A.map) { - const ap = aBin.p; - const aCount = aBin.count; - const aAttr = aBin.attr; - for (const [bVal, bBin] of B.map) { - const bp = bBin.p; - const dmg = aVal + bVal; - let dest = combinedMap.get(dmg); - if (dest === void 0) { - dest = { p: 0, count: {} }; - combinedMap.set(dmg, dest); - } - dest.p += ap * bp; - const dc = dest.count; - for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; - for (const k in bBin.count) - dc[k] = (dc[k] || 0) + bBin.count[k] * ap; - if (aAttr || bBin.attr) { - let da = dest.attr; - if (da === void 0) { - da = {}; - dest.attr = da; - } - if (aAttr) - for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; - if (bBin.attr) - for (const k in bBin.attr) - da[k] = (da[k] || 0) + bBin.attr[k] * ap; - } - } - } - let result = new _PMF( - combinedMap, - epsilon, - !raw, - `${A.identifier}${raw ? "*" : "+"}${B.identifier}` - ); - const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); - const mGot = result.mass(); - if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { - result = result.scaleMass(mExp / mGot); - } - if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) - result = result.normalize(); - pmfCache?.set(cacheKey, result); - return result; - } - // 3) Nice wrapper so you can call pmf.combineRaw(other) - combineRaw(other, eps) { - return this.convolve(other, eps, true); - } - // Reduce a list of PMFs by left-folding convolve() with the given eps - static reduceConvolveLeft(pmfList, eps) { - let result = pmfList[0]; - for (let i = 1; i < pmfList.length; i++) { - result = result.convolve(pmfList[i], eps); - } - return result; - } - /** - * Convolves multiple PMFs using linear convolution with automatic caching. - * Uses a left-to-right accumulation approach for maximum cache reuse. - * Each convolve() call automatically uses the convolution cache for performance. - * - * This linear approach provides better cache hits than pairwise because: - * - Intermediate results are more predictable and stable - * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) - * - Order-independent cache keys work better with consistent build patterns - */ - static convolveMany(pmfList, eps = EPS) { - if (pmfList.length === 0) return _PMF.empty(eps); - if (pmfList.length === 1) return pmfList[0]; - return _PMF.reduceConvolveLeft(pmfList, eps); - } - /** - * Returns a plain, JSON-serializable representation of this PMF. - * - * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces - * the expected output (no double-encoding). Use {@link PMF.fromJSON} to - * reconstruct, or {@link PMF.toJSONString} if you need the string directly. - */ - toJSON() { - return { - bins: [...this.map.entries()], - normalized: this.normalized, - identifier: this.identifier - }; - } - /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ - toJSONString() { - return JSON.stringify(this); - } - static fromJSON(jsonData) { - return new _PMF( - new Map(jsonData.bins), - EPS, - !!jsonData.normalized, - jsonData.identifier || "fromJSON" - ); - } - /** - * Relative pruning with optional top-K floor. - * Keeps bins with p >= epsRel * peak, always keeps min and max damage, - * optionally guarantees at least `minBins` survivors by adding top-K. - * Returns a new, non-normalized PMF. - */ - prune(epsRel, minBins = 0) { - const size = this.map.size; - if (size === 0) return this; - let peak = 0; - let minDamage = Number.POSITIVE_INFINITY; - let maxDamage = Number.NEGATIVE_INFINITY; - for (const [dmg, bin] of this.map) { - if (bin.p > peak) peak = bin.p; - if (dmg < minDamage) minDamage = dmg; - if (dmg > maxDamage) maxDamage = dmg; - } - if (peak === 0) - return new _PMF(new Map(this.map), epsRel, false, this.identifier); - const thresh = epsRel * peak; - const entries = [...this.map.entries()]; - const survivorsByDmg = /* @__PURE__ */ new Map(); - const protect = (d) => { - const b = this.map.get(d); - if (b) survivorsByDmg.set(d, b); - }; - protect(minDamage); - if (maxDamage !== minDamage) protect(maxDamage); - for (const [dmg, bin] of entries) { - if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); - } - if (minBins > 0 && survivorsByDmg.size < minBins) { - entries.sort((a, b) => b[1].p - a[1].p); - for (const [dmg, bin] of entries) { - if (!survivorsByDmg.has(dmg)) { - survivorsByDmg.set(dmg, bin); - if (survivorsByDmg.size >= minBins) break; - } - } - } - const prunedMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of survivorsByDmg) { - const newCount = {}; - for (const k in bin.count) { - const v = bin.count[k]; - if (Math.abs(v) >= thresh) newCount[k] = v; - } - let newAttr; - if (bin.attr) { - for (const k in bin.attr) { - const v = bin.attr[k]; - if (Math.abs(v) >= thresh) { - if (!newAttr) newAttr = {}; - newAttr[k] = v; - } - } - } - prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); - } - return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); - } - /** Probability mass at exactly x. */ - pAt(x) { - return this.map.get(x)?.p ?? 0; - } - /** - * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. - * Assumes a miss is encoded as the damage-0 bin (the convention used across - * attack/save PMFs). The dual of {@link missProbability}. - */ - hitProbability() { - return 1 - this.pAt(0); - } - /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ - missProbability() { - return this.pAt(0); - } - /** - * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width - * damage buckets, aggregating probability mass (and `count`/`attr` - * provenance) into each bucket's start value. Returns this PMF unchanged when - * its integer support already fits within `maxBuckets`. - * - * This is a lossy display/downsampling transform (bucket start replaces the - * exact damage value) — use it for charting wide distributions, not for DPR - * math. - */ - rebin(maxBuckets) { - if (!(maxBuckets > 0)) return this; - const support = this.support(); - if (support.length === 0) return this; - const min = support[0]; - const max = support[support.length - 1]; - const range = max - min; - if (range + 1 <= maxBuckets) return this; - const binSize = Math.ceil((range + 1) / maxBuckets); - return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize); - } - /** Dense integer support from min..max (inclusive). - * Useful for showing empty bars in charts. - */ - denseSupport() { - const s = this.support(); - if (s.length === 0) return []; - const lo = Math.min(...s), hi = Math.max(...s); - return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( - (a, b) => a - b - ); - } - /** CDF at x: P(X ≤ x). */ - cdfAt(x) { - let acc = 0; - for (const [val, bin] of this.map) if (val <= x) acc += bin.p; - return acc; - } - /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ - quantile(p) { - if (this.map.size === 0) return 0; - const s = this.support().sort((a, b) => a - b); - let acc = 0; - for (const x of s) { - acc += this.pAt(x); - if (acc >= p) return x; - } - return s[s.length - 1]; - } - /** Get outcome probability at specific damage value. */ - outcomeAt(damage, outcome) { - return this.map.get(damage)?.count[outcome] ?? 0; - } - /** Get all outcome types present in this PMF. */ - outcomes() { - const outcomeSet = /* @__PURE__ */ new Set(); - for (const [, bin] of this.map) { - for (const outcome in bin.count) { - if (bin.count[outcome] > 0) { - outcomeSet.add(outcome); - } - } - } - return Array.from(outcomeSet).sort(); - } - /** Get total probability of an outcome across all damage values. */ - outcomeProbability(outcome) { - let total = 0; - for (const [, bin] of this.map) { - total += bin.count[outcome] ?? 0; - } - return total; - } - /** Get damage attribution for an outcome at specific damage value. */ - outcomeAttributionAt(damage, outcome) { - return this.map.get(damage)?.attr?.[outcome] ?? 0; - } - /** Get all outcome data at specific damage value. */ - binAt(damage) { - const bin = this.map.get(damage); - if (!bin) return null; - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Check if outcome exists in this PMF. */ - hasOutcome(outcome) { - for (const [, bin] of this.map) { - if ((bin.count[outcome] ?? 0) > 0) { - return true; - } - } - return false; - } - /** - * Split each damage value's probability mass across outcome labels, returning - * per-label maps of `damage value → probability mass attributable to that - * label`. Summing over labels at a given value recovers that value's `p`. - * - * Damage-bearing bins are split by `attr` weight (the share of damage each - * outcome contributed); the clean-miss bin at 0 is split by `count` weight - * (there is no damage to attribute). Attribution is computed on demand via - * {@link withAttribution} when absent, so builder-generated PMFs work too. - * - * This is the provenance core of the stacked damage-attribution chart — the - * caller only maps these series into its rendering format (colors, binning, - * axis labels). - */ - attributionByValue() { - const src = this.hasAttribution() ? this : this.withAttribution(); - const result = /* @__PURE__ */ new Map(); - const add = (label, damage, mass) => { - if (!(mass > 0)) return; - let series = result.get(label); - if (!series) { - series = /* @__PURE__ */ new Map(); - result.set(label, series); - } - series.set(damage, (series.get(damage) ?? 0) + mass); - }; - for (const [damage, bin] of src.map) { - const p = bin.p || 0; - if (p <= 0) continue; - const isMissBin = damage === 0; - if (isMissBin) { - let totalCount = 0; - for (const k in bin.count) totalCount += bin.count[k] || 0; - if (totalCount > 0) { - const c = bin.count[MISS_NONE_OUTCOME] || 0; - add(MISS_NONE_OUTCOME, damage, c / totalCount * p); - } - continue; - } - let totalAttr = 0; - if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; - if (bin.attr && totalAttr > 0) { - for (const k in bin.attr) { - if (k === MISS_NONE_OUTCOME) continue; - add(k, damage, (bin.attr[k] || 0) / totalAttr * p); - } - } - } - return result; - } - tailProbGE(t) { - let s = 0; - for (const [x, bin] of this) { - if (bin.p > 0 && x >= t) s += bin.p; - } - return s; - } - tailProbGT(t) { - let s = 0; - for (const [x, rec] of this) { - if (x > t) s += rec.p; - } - return s; - } - /** - * Returns a new PMF containing only bins where the specified outcome has non-zero probability. - * This creates a marginal distribution for the given outcome type, with probabilities - * scaled to represent the unconditional mass attributable to that outcome. - */ - filterOutcome(outcome) { - const filteredMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of this.map) { - const outcomeCount = bin.count[outcome] ?? 0; - const totalCount = Object.values(bin.count ?? {}).reduce( - (a, b) => (a ?? 0) + (b ?? 0), - 0 - ); - if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { - const proportion = outcomeCount / totalCount; - const newP = bin.p * proportion; - const newCount = { [outcome]: outcomeCount }; - let newAttr; - if (bin.attr && bin.attr[outcome] !== void 0) { - newAttr = { [outcome]: bin.attr[outcome] * proportion }; - } - filteredMap.set(damageValue, { - p: newP, - count: newCount, - attr: newAttr - }); - } - } - return new _PMF( - filteredMap, - this.epsilon, - false, - // don't normalize by default - `filter(${this.identifier},${outcome})` - ); - } - /** - * Calculates probabilities for first-success outcomes across n independent attempts. - * - * @param pSuccess - Total probability of any success on a single attempt. - * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). - * @param n - Number of independent attempts. - * - * Returns: - * - pSpecificSuccess: Probability that the first success was of the "special" type - * - pGeneralSuccess: Probability that the first success was of the non-special type - * - pNone: Probability that no successes occurred - * - pAny: Probability that at least one success occurred - */ - static firstSuccessWeights(pSuccess, pSpecial, n) { - if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { - throw new Error( - `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` - ); - } - const pFail = 1 - pSuccess; - const pFailAll = Math.pow(pFail, n); - const pAny = 1 - pFailAll; - const denom = pSuccess === 0 ? 1 : pSuccess; - const pSpecificSuccess = pSpecial * pAny / denom; - const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; - const pNone = 1 - pSpecificSuccess - pGeneralSuccess; - return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; - } - mapValues(f, eps = EPS, opts) { - const rounding = opts?.rounding ?? "none"; - const preserveCounts = opts?.preserveCounts ?? true; - const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; - const probs = /* @__PURE__ */ new Map(); - const counts = /* @__PURE__ */ new Map(); - for (const [v, bin] of this) { - if (Math.abs(bin.p) < eps) continue; - const u = round(f(v)); - probs.set(u, (probs.get(u) ?? 0) + bin.p); - if (preserveCounts) { - const src = bin.count; - if (src) { - const dest = counts.get(u) ?? {}; - for (const k in src) { - dest[k] = (dest[k] ?? 0) + src[k]; - } - counts.set(u, dest); - } - } - } - const internal = /* @__PURE__ */ new Map(); - for (const [u, p] of probs) { - internal.set(u, { p, count: counts.get(u) ?? {} }); - } - return _PMF.fromMap( - new Map(Array.from(internal, ([u, b]) => [u, b.p])), - eps - ); - } - static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { - const filtered = []; - for (const [v, p] of m) { - if (!Number.isFinite(v) || !Number.isFinite(p)) continue; - if (p <= 0 || Math.abs(p) < eps) continue; - if (requireIntegerValues && !Number.isInteger(v)) { - throw new Error(`fromMap: non-integer outcome ${v}`); - } - filtered.push([v, p]); - } - if (filtered.length === 0) { - throw new Error("fromMap: empty or invalid input map"); - } - let sum = 0; - let c = 0; - for (const [, p] of filtered) { - const y = p - c; - const t = sum + y; - c = t - sum - y; - sum = t; - } - if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); - filtered.sort((a, b) => a[0] - b[0]); - const internal = /* @__PURE__ */ new Map(); - for (const [v, p] of filtered) { - internal.set(v, { p: p / sum, count: {} }); - } - return new _PMF(internal, eps); - } - query() { - return new DiceQuery(this); - } -}; -// Unique ID generator for anonymous PMFs to avoid cache key collisions -_PMF.__anonIdCounter = 1; -var PMF = _PMF; - -// src/parser/dice.ts -var MAX_BINARY_OUTCOMES = 1e8; -var Dice = class _Dice { - constructor(x = 0) { - this.faces = {}; - this.privateData = {}; - // Partial: the object starts empty and gains keys as outcomes are recorded, - // so the type must not claim every OutcomeType is present. (Previously typed - // as a full Record via an `as` cast, which lied about missing keys.) - this.outcomeData = {}; - this.hasHitDistributionCalculated = false; - if (x <= 0) return; - for (let i = 1; i <= x; i++) { - this.faces[i] = 1; - } - } - getOutcomeDistribution(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const distribution = this.outcomeData[key]; - if (distribution === void 0) return void 0; - return { ...distribution }; - } - getFullOutcomeDistribution() { - return { ...this.outcomeData }; - } - setOutcomeDistribution(key, data) { - if (data) { - this.outcomeData[key] = data; - } else { - delete this.outcomeData[key]; - } - } - hasOutcomeData(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const data = this.outcomeData[key]; - return data !== void 0 && Object.keys(data).length > 0; - } - getOutcomeCount(key, face) { - return this.outcomeData[key]?.[face] ?? 0; - } - getAverage(key) { - const distribution = this.getOutcomeDistribution(key); - if (!distribution) return 0; - const totalCount = Object.values(distribution).reduce( - (sum, count) => sum + count, - 0 - ); - const expectedDamage = Object.entries(distribution).reduce( - (sum, [damage, count]) => sum + Number(damage) * count, - 0 - ); - if (totalCount === 0) return 0; - return expectedDamage / totalCount; - } - // TODO this can be private later if we change how testing works - calculateHitDistribution() { - const hitValues = {}; - const subtractedOutcomes = [ - this.outcomeData.crit, - this.outcomeData.missNone, - this.outcomeData.missDamage, - this.outcomeData.saveHalf, - this.outcomeData.saveFail, - this.outcomeData.pc - ]; - for (const [face, totalCount] of Object.entries(this.faces)) { - const numFace = Number(face); - let hitCount = totalCount; - for (const distribution of subtractedOutcomes) { - const outcomeCount = distribution?.[numFace]; - if (outcomeCount) { - hitCount -= outcomeCount; - } - } - if (numFace === 0) { - hitCount = 0; - } - if (hitCount < 0) { - hitCount = 0; - } - hitValues[numFace] = hitCount; - } - return hitValues; - } - ensureHitDistribution() { - if (!this.hasHitDistributionCalculated) { - const hitValues = this.calculateHitDistribution(); - this.setOutcomeDistribution("hit", hitValues); - this.hasHitDistributionCalculated = true; - } - } - // PRIVATE FUNCTIONS - binaryOp(other, op, diceConstructor) { - const result = diceConstructor ? diceConstructor() : new _Dice(); - const isScalar = typeof other === "number"; - const keys1 = this.keys(); - const keys2 = isScalar ? [] : other.keys(); - if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { - throw new DiceParseError( - `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` - ); - } - for (const key1 of keys1) { - const value1 = this.faces[key1]; - if (isScalar) { - const resultKey = op(key1, other); - result.increment(resultKey, value1); - } else { - for (const key2 of keys2) { - const value2 = other.faces[key2]; - const resultKey = op(key1, key2); - result.increment(resultKey, value1 * value2); - } - } - } - return result; - } - removeFaces(facesToRemove) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (!facesToRemove.includes(numKey)) { - result.faces[numKey] = value; - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // PUBLIC FUNCTIONS - getFaceEntries() { - return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); - } - getFaceMap() { - return { ...this.faces }; - } - get(face) { - return this.faces[face] ?? 0; - } - keys() { - return Object.keys(this.faces).map(Number); - } - values() { - return Object.values(this.faces); - } - total() { - return Object.values(this.faces).reduce((sum, value) => sum + value, 0); - } - setFace(key, value) { - this.faces[key] = value; - } - static scalar(value) { - const result = new _Dice(); - result.increment(value, 1); - return result; - } - maxFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.max(...numericKeys); - } - minFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.min(...numericKeys); - } - increment(face, count) { - const current = this.faces[face] || 0; - this.faces[face] = current + count; - } - normalize(scalar) { - const result = new _Dice(); - for (const [face, count] of Object.entries(this.faces)) { - result.faces[Number(face)] = count * scalar; - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // OPERATIONS - add(other) { - return this.binaryOp(other, (a, b) => a + b); - } - subtract(other) { - return this.binaryOp(other, (a, b) => a - b); - } - conditionalApply(other) { - return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); - } - multiply(other) { - return this.binaryOp(other, (a, b) => a * b); - } - addNonZero(other) { - return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); - } - eq(other) { - return this.binaryOp(other, (a, b) => a === b ? 1 : 0); - } - max(other) { - return this.binaryOp(other, (a, b) => Math.max(a, b)); - } - min(other) { - return this.binaryOp(other, (a, b) => Math.min(a, b)); - } - advantage() { - return this.max(this); - } - ge(other) { - return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); - } - divide(other) { - return this.binaryOp(other, (a, b) => a / b); - } - divideRoundUp(other) { - return this.binaryOp(other, (a, b) => Math.ceil(a / b)); - } - divideRoundDown(other) { - return this.binaryOp(other, (a, b) => Math.floor(a / b)); - } - and(other) { - return this.binaryOp(other, (a, b) => a && b ? 1 : 0); - } - checkTarget(other, comparisonLogic) { - const createResult = () => { - const result = new _Dice(); - result.increment(0, 0); - result.increment(1, 0); - return result; - }; - return this.binaryOp(other, comparisonLogic, createResult); - } - dc(other) { - const dcCheck = (a, b) => a >= b ? 0 : 1; - const result = this.checkTarget(other, dcCheck); - result.privateData.isDCCheck = true; - return result; - } - ac(other) { - const acCheck = (a, b) => a >= b ? a : 0; - return this.checkTarget(other, acCheck); - } - deleteFace(face) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (numKey !== face) { - result.increment(numKey, value); - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - reroll(toReroll) { - const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; - const rerollKeys = rerollDice.keys(); - const rerollSet = new Set(rerollKeys); - const removed = this.removeFaces(rerollKeys); - let result = new _Dice(); - for (const face of this.keys()) { - const wasRerolled = rerollSet.has(face); - result = result.combine(removed); - if (wasRerolled) { - result = result.combine(this); - } - } - return result; - } - // This is not addition and not rolling two dice at once. - // Instead, it’s mixing two distributions into a single weighted die. - combine(other) { - if (typeof other === "number") { - other = _Dice.scalar(other); - } - const result = new _Dice(); - for (const [key, value] of Object.entries(other.faces)) { - result.faces[Number(key)] = value; - } - const except = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - result.increment(numKey, value); - if (!(numKey in other.faces)) { - except.increment(numKey, value); - } - } - result.privateData = { ...this.privateData, except: other }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - combineInPlace(other) { - for (const [key, value] of Object.entries(other.faces)) { - const numKey = Number(key); - const current = this.faces[numKey] || 0; - this.faces[numKey] = current + value; - } - } - percent() { - const total = this.total(); - const result = {}; - for (const [face, count] of Object.entries(this.faces)) { - result[Number(face)] = count / total; - } - return result; - } - average() { - const total = this.total(); - if (total === 0) return 0; - let sum = 0; - for (const [key, value] of Object.entries(this.faces)) { - sum += Number(key) * value; - } - return sum / total; - } - /* - * Convert dice to PMF using OutcomeType labels directly from damage distribution. - * This is much cleaner than the original complex distribution conversion. - */ - toPMF(numEpsilon = EPS) { - const total = this.total(); - if (total === 0) return PMF.empty(numEpsilon); - this.ensureHitDistribution(); - const map = /* @__PURE__ */ new Map(); - const hitDistro = this.getOutcomeDistribution("hit") || {}; - const critDistro = this.getOutcomeDistribution("crit") || {}; - const missDistro = this.getOutcomeDistribution("missDamage") || {}; - const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; - const pcDistro = this.getOutcomeDistribution("pc") || {}; - const isSaveHalf = Object.keys(saveDistro).length > 0; - const isDCCheck = this.privateData.isDCCheck === true; - const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; - for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { - const face = Number(faceStr); - const faceCount = Number(faceCountRaw); - if (faceCount <= 0) continue; - let p = faceCount / total; - p = clampNonNeg(p); - if (!(p > 0)) continue; - if (numEpsilon >= 0 && p < numEpsilon) continue; - const count = {}; - const attr = {}; - if (hitDistro[face]) { - const c = clampNonNeg(hitDistro[face] / total); - if (c > 0) { - if (isSaveHalf || isDCCheck) { - count.saveFail = c; - attr.saveFail = clampNonNeg(face * hitDistro[face] / total); - } else { - count.hit = c; - attr.hit = clampNonNeg(face * hitDistro[face] / total); - } - } - } - if (critDistro[face]) { - const c = clampNonNeg(critDistro[face] / total); - if (c > 0) { - count.crit = c; - attr.crit = clampNonNeg(face * critDistro[face] / total); - } - } - if (missDistro[face]) { - const c = clampNonNeg(missDistro[face] / total); - if (c > 0) { - count.missDamage = c; - attr.missDamage = clampNonNeg(face * missDistro[face] / total); - } - } - if (saveDistro[face]) { - const c = clampNonNeg(saveDistro[face] / total); - if (c > 0) { - if (isSaveHalf) { - count.saveHalf = c; - attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); - } else { - count.saveFail = (count.saveFail ?? 0) + c; - attr.saveFail = clampNonNeg( - (attr.saveFail ?? 0) + face * saveDistro[face] / total - ); - } - } - } - if (pcDistro[face]) { - const c = clampNonNeg(pcDistro[face] / total); - if (c > 0) { - count.pc = c; - attr.pc = clampNonNeg(face * pcDistro[face] / total); - } - } - if (!isSaveHalf && !isDCCheck) { - const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); - const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); - if (unaccountedCount > 0) { - const frac = clampNonNeg(unaccountedCount / total); - if (frac > 0) { - count.missNone = (count.missNone ?? 0) + frac; - } - } - } - const bin = { p, count }; - if (Object.keys(attr).length > 0) { - bin.attr = attr; - } - map.set(face, bin); - } - const identifier = this.identifier || "ERROR"; - return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); - } -}; - -// src/parser/parser.ts -var MAX_DIE_SIDES = 1e6; -var MAX_DICE_COUNT = 1e4; -var MAX_KEEP_OUTCOMES = 1e6; -var parseCache = new LRUCache(1e3); -var cachingEnabled = true; -function setCachingEnabled(enabled) { - cachingEnabled = enabled; - if (!enabled) clearParserCache(); -} -function getCachingEnabled() { - return cachingEnabled; -} -function clearParserCache() { - parseCache.clear(); -} -function parse(expression, n = 0) { - const cleaned = expression.replace(/ /g, "").toLowerCase(); - if (cachingEnabled) { - const cacheKey = `${cleaned}:${n}`; - const cached = parseCache.get(cacheKey); - if (cached) return cached; - } - const chars = [...cleaned]; - let result; - try { - result = parseExpression(chars, n); - } catch (error) { - throw new DiceParseError( - `Cannot parse dice expression [${expression}]: ${error}`, - { expression, cause: error } - ); - } - result.privateData = result.privateData || {}; - result.identifier = cleaned; - if (chars.length > 0) { - throw new DiceParseError( - `Unexpected token: '${chars[0]}' from expression: '${expression}'`, - { expression } - ); - } - const resultPMF = result.toPMF(-1); - if (cachingEnabled) { - const cacheKey = `${cleaned}:${n}`; - parseCache.set(cacheKey, resultPMF); - } - return resultPMF; -} -function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { - dice = dice.normalize(currentNorm); - finalResult = finalResult.normalize(normValue); - finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); - finalResult = finalResult.combine(dice); - return { newNorm: currentNorm * normValue, updatedResult: finalResult }; -} -function parseExpression(arr, n) { - const result = (() => { - const res = parseArgument(arr, n); - return typeof res === "number" ? Dice.scalar(res) : res; - })(); - let op = parseOperation(arr); - let finalResult = result; - while (op != null) { - const arg = !op.unary ? parseArgument(arr, n) : finalResult; - let crit; - let critNorm = 1; - if (arr[0] === "x" || arr[0] === "c") { - const isXcrit = arr[0] === "x"; - if (isXcrit) assertToken(arr, "x"); - assertToken(arr, "c"); - assertToken(arr, "r"); - assertToken(arr, "i"); - assertToken(arr, "t"); - const count = isXcrit ? parseNumber(arr, n) : 1; - crit = new Dice(); - for (let i = 0; i < count; i++) { - const max = finalResult.maxFace(); - crit.setFace(max, finalResult.get(max)); - finalResult = finalResult.deleteFace(max); - } - critNorm = crit.total(); - crit = op.call(crit, parseBinaryArgument(arg, arr, n)); - critNorm = crit && critNorm ? crit.total() / critNorm : 1; - } - let save; - let saveNorm = 1; - if (arr[0] === "s") { - assertToken(arr, "s"); - assertToken(arr, "a"); - assertToken(arr, "v"); - assertToken(arr, "e"); - save = new Dice(); - const min = finalResult.minFace(); - save.increment(min > 0 ? min : 1, finalResult.get(min)); - saveNorm = save.total(); - finalResult = finalResult.deleteFace(min); - save = op.call(save, parseBinaryArgument(arg, arr, n)); - saveNorm = save && saveNorm ? save.total() / saveNorm : 1; - } - let pc; - let pcNorm = 1; - if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { - assertToken(arr, "p"); - assertToken(arr, "c"); - pc = new Dice(); - const min = finalResult.minFace(); - pc.increment(min > 0 ? min : 1, finalResult.get(min)); - const missBefore = pc.total(); - finalResult = finalResult.deleteFace(min); - pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); - const missAfter = pc ? pc.total() : 0; - pcNorm = missBefore ? missAfter / missBefore : 1; - } - let miss; - let missNorm = 1; - if (arr[0] === "m") { - assertToken(arr, "m"); - assertToken(arr, "i"); - assertToken(arr, "s"); - assertToken(arr, "s"); - miss = new Dice(); - const min = finalResult.minFace(); - miss.increment(min > 0 ? min : 1, finalResult.get(min)); - missNorm = miss.total(); - finalResult = finalResult.deleteFace(min); - miss = op.call(miss, parseBinaryArgument(arg, arr, n)); - missNorm = miss && missNorm ? miss.total() / missNorm : 1; - } - let norm = finalResult.total(); - finalResult = op.call(finalResult, arg); - norm = norm ? finalResult.total() / norm : 1; - if (crit) { - const result2 = combineDiceWithNormalization( - crit, - critNorm, - "crit", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (save) { - const result2 = combineDiceWithNormalization( - save, - saveNorm, - "saveHalf", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (miss) { - const result2 = combineDiceWithNormalization( - miss, - missNorm, - "missDamage", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (pc) { - const result2 = combineDiceWithNormalization( - pc, - pcNorm, - "pc", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - op = parseOperation(arr); - } - return finalResult; -} -function parseArgument(s, n) { - let result = parseArgumentInternal(s, n); - while (true) { - const next = parseArgumentInternal(s, n); - if (next === void 0) break; - result = multiplyDiceByDice(result, next); - } - return result; -} -function multiplyDiceByDice(d1, d2) { - if (typeof d1 === "number") d1 = Dice.scalar(d1); - if (typeof d2 === "number") d2 = Dice.scalar(d2); - const result = new Dice(); - const faces = /* @__PURE__ */ new Map(); - let normalizationFactor = 1; - for (const key of d1.keys()) { - let face; - if (typeof key !== "number") { - continue; - } - if (d2.privateData.keep) { - const faceCount = d2.keys().length; - if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { - throw new DiceParseError( - `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` - ); - } - const repeat = Array(key).fill(d2); - face = opDice(repeat, d2.privateData.keep); - } else { - face = multiplyDice(key, d2); - } - normalizationFactor *= face.total(); - faces.set(key, face); - } - for (const [k, face] of faces) { - const count = d1.get(k); - result.combineInPlace( - face.normalize(count * normalizationFactor / face.total()) - ); - } - result.privateData.except = {}; - return result; -} -function multiplyDice(n, d) { - if (n > MAX_DICE_COUNT) { - throw new DiceParseError( - `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` - ); - } - if (n === 0) return new Dice(0); - if (n === 1) return d; - const half = Math.floor(n / 2); - let result = multiplyDice(half, d); - result = result.add(result); - if (n % 2 === 1) { - result = result.add(d); - } - return result; -} -function opDice(diceList, keepFn) { - return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); -} -function opDiceInternal(diceList, result, index, values, weight, combineFn) { - if (index === diceList.length) { - return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); - } - const currentDice = diceList[index]; - for (const face of currentDice.keys()) { - values.push(face); - result = opDiceInternal( - diceList, - result, - index + 1, - values, - weight * currentDice.get(face), - combineFn - ); - values.pop(); - } - return result; -} -function parseArgumentInternal(s, n) { - if (s.length === 0) return; - const c = s[0]; - switch (c) { - case "(": - s.shift(); - return assertToken(s, ")", parseExpression(s, n)); - case "h": - case "d": - return parseDice(s, n); - case "k": - assertToken(s, "k"); - return parseKeep(s, n); - case "n": - return parseNumber(s, n); - default: - if (isDigit(c)) return parseNumber(s, n); - return; - } -} -function parseBinaryArgument(arg, arr, n) { - if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { - assertToken(arr, "half"); - const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; - return diceArg.divideRoundDown(2); - } - const parsed = parseArgument(arr, n); - return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; -} -function assertToken(s, expected, ret) { - for (const ch of expected) { - const found = s.shift(); - if (found !== ch) { - throw new Error(`Expected character '${ch}', found '${found}'`); - } - } - return ret; -} -function parseDice(s, n) { - let rerollOne = false; - if (peek(s, "hd") && peekIsNumber(s, 2)) { - assertToken(s, "h"); - assertToken(s, "d"); - rerollOne = true; - } else if (peek(s, "d") && peekIsNumber(s, 1)) { - assertToken(s, "d"); - } else { - return; - } - const sides = parseNumber(s, n); - if (sides > MAX_DIE_SIDES) { - throw new DiceParseError( - `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` - ); - } - let result = new Dice(sides); - if (rerollOne) { - result = result.reroll(1); - } - return result; -} -function peek(arr, expected) { - if (expected.length > arr.length) return false; - for (let i = 0; i < expected.length; i++) { - if (arr[i] !== expected.charAt(i)) return false; - } - return true; -} -function peekIsNumber(arr, index) { - if (index >= arr.length) return false; - return isDigit(arr[index]) || arr[index] === "n"; -} -function parseNumber(s, n) { - let ret = ""; - while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { - const ch = s.shift(); - ret += ch === "n" ? n.toString() : ch; - } - if (ret.length === 0) { - throw new Error(`Expected number, found: '${s[0]}'`); - } - return parseInt(ret, 10); -} -function isDigit(c) { - return c >= "0" && c <= "9"; -} -function parseKeep(s, n) { - let keepLowest = false; - if (peek(s, "l")) { - assertToken(s, "l"); - keepLowest = true; - } else if (peek(s, "h")) { - assertToken(s, "h"); - keepLowest = false; - } else { - return; - } - const keepCount = parseNumber(s, n); - const result = parseArgumentInternal(s, n); - if (result instanceof Dice) { - result.privateData.keep = keepN(keepCount, keepLowest); - return result; - } - throw new Error("Expected Dice after keep modifier"); -} -function keepN(n, low) { - return (values) => { - const sorted = [...values].sort((a, b) => low ? a - b : b - a); - return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); - }; -} -function parseOperation(s) { - switch (s[0]) { - case ")": - return; - case "a": - assertToken(s, "ac"); - return Dice.prototype.ac; - case "d": - assertToken(s, "dc"); - return Dice.prototype.dc; - case "!": - assertToken(s, "!"); - const adv = Dice.prototype.advantage; - adv.unary = true; - return adv; - case ">": - assertToken(s, ">"); - return Dice.prototype.max; - case "<": - assertToken(s, "<"); - return Dice.prototype.min; - case "+": - assertToken(s, "+"); - return Dice.prototype.addNonZero; - case "~": - assertToken(s, "~"); - assertToken(s, "+"); - return Dice.prototype.add; - case "-": - assertToken(s, "-"); - return Dice.prototype.subtract; - case "&": - assertToken(s, "&"); - return Dice.prototype.combine; - case "r": - assertToken(s, "reroll"); - return Dice.prototype.reroll; - case "*": - assertToken(s, "*"); - if (peek(s, "*")) { - assertToken(s, "*"); - return Dice.prototype.multiply; - } - return Dice.prototype.conditionalApply; - case "/": - assertToken(s, "/"); - if (s[0] === "/") { - assertToken(s, "/"); - return Dice.prototype.divideRoundDown; - } - return Dice.prototype.divideRoundUp; - case "=": - assertToken(s, "="); - return Dice.prototype.eq; - } - return; -} - -// src/pmf/mixture.ts -var Mixture = class _Mixture { - constructor(eps = EPS) { - this.totals = /* @__PURE__ */ new Map(); - // raw mass per outcome (pre-normalization) - this.labelMass = /* @__PURE__ */ new Map(); - this.eps = Number.isFinite(eps) ? eps : EPS; - } - /** Remove all accumulated state. */ - clear() { - this.totals.clear(); - this.labelMass.clear(); - return this; - } - /** Number of distinct outcome values currently accumulated. */ - size() { - return this.totals.size; - } - /** Whether a label was ever added. */ - hasLabel(label) { - for (const bag of this.labelMass.values()) if (bag[label]) return true; - return false; - } - /** - * Add a labeled component with a mixture weight. - * Weight can be any positive finite number. Very small contributions are pruned by eps. - */ - add(label, pmf, weight = 1) { - if (!Number.isFinite(weight) || weight <= 0) return this; - for (const [v, bin] of pmf) { - const p = bin.p; - if (p <= 0) continue; - const add = weight * p; - if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; - this.totals.set(v, (this.totals.get(v) ?? 0) + add); - const bag = this.labelMass.get(v) ?? {}; - bag[label] = (bag[label] ?? 0) + add; - this.labelMass.set(v, bag); - } - return this; - } - buildPMF(eps = EPS) { - let grand = 0; - let c = 0; - for (const m of this.totals.values()) { - const y = m - c; - const t = grand + y; - c = t - grand - y; - grand = t; - } - if (!(grand > 0)) throw new Error("Mixture: zero total mass"); - const internal = /* @__PURE__ */ new Map(); - for (const [v, m] of this.totals) { - if (m <= 0 || Math.abs(m) < this.eps) continue; - const count = this.labelMass.get(v) ?? {}; - internal.set(v, { p: m / grand, count }); - } - return new PMF(internal, eps); - } - /** - * Produce normalized *per-label* PMFs (labels independent). - * These are unlabeled PMFs built from the raw mass of that label alone. - */ - byOutcome() { - const labels = /* @__PURE__ */ new Set(); - for (const bag of this.labelMass.values()) { - for (const k of Object.keys(bag)) labels.add(k); - } - const out = {}; - for (const label of labels) { - const m = /* @__PURE__ */ new Map(); - for (const [v, bag] of this.labelMass) { - const w = bag[label]; - if (w && Math.abs(w) >= this.eps) m.set(v, w); - } - if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); - } - return out; - } - /** - * Mixture weights per label, normalized to sum to 1 over labels that appeared. - * Uses raw mass before per-outcome normalization. - */ - weights() { - const res = {}; - for (const [, bag] of this.labelMass) { - for (const [lab, w] of Object.entries(bag)) { - if (!Number.isFinite(w) || w <= 0) continue; - res[lab] = (res[lab] ?? 0) + w; - } - } - let total = 0; - let c = 0; - for (const v of Object.values(res)) { - const y = v - c; - const t = total + y; - c = t - total - y; - total = t; - } - if (total > 0) { - for (const k in res) res[k] = res[k] / total; - } - return res; - } - toJSON() { - return { - totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), - labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), - eps: this.eps - }; - } - static mix(items, eps = EPS) { - const mix = new _Mixture(eps); - for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); - return mix.buildPMF(); - } -}; - -exports.ALL_OUTCOME_TYPES = ALL_OUTCOME_TYPES; -exports.DiceParseError = DiceParseError; -exports.DiceQuery = DiceQuery; -exports.EPS = EPS; -exports.LRUCache = LRUCache; -exports.MISS_NONE_OUTCOME = MISS_NONE_OUTCOME; -exports.Mixture = Mixture; -exports.OUTCOME_DISPLAY_ORDER = OUTCOME_DISPLAY_ORDER; -exports.PMF = PMF; -exports.calculateBounceOdds = calculateBounceOdds; -exports.clearParserCache = clearParserCache; -exports.critProbability = critProbability; -exports.getCachingEnabled = getCachingEnabled; -exports.onAnyHit = onAnyHit; -exports.onCritOnly = onCritOnly; -exports.onHitOnly = onHitOnly; -exports.onMissDamageOnly = onMissDamageOnly; -exports.onMissOnly = onMissOnly; -exports.onPotentCantripOnly = onPotentCantripOnly; -exports.onSaveFailOnly = onSaveFailOnly; -exports.onSaveHalfOnly = onSaveHalfOnly; -exports.parse = parse; -exports.pmfCache = pmfCache; -exports.setCachingEnabled = setCachingEnabled; -exports.sortOutcomes = sortOutcomes; -//# sourceMappingURL=index.cjs.map -//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/dist/index.cjs.map b/dist/index.cjs.map deleted file mode 100644 index 5c54f4d..0000000 --- a/dist/index.cjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/common/bounce.ts","../src/common/errors.ts","../src/common/lru-cache.ts","../src/common/types.ts","../src/pmf/query.ts","../src/pmf/pmf.ts","../src/parser/dice.ts","../src/parser/parser.ts","../src/pmf/mixture.ts"],"names":["cumulativeSum","result"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Bounce odds — the \"birthday problem\" for bouncing damage dice (e.g. Chromatic\n * Orb): the probability that at least two of K dice with S faces show the same\n * value, which is what lets the spell jump to another target.\n *\n * Accounts for two modifiers:\n * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are\n * bumped up to it, collapsing the low faces onto a single heavier value.\n * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be\n * rerolled once, giving a second chance at a match.\n *\n * The base and Elemental-Adept cases are computed exactly (see\n * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered\n * on the exact base match probability.\n */\n\n/** Options that modify bounce odds via metamagic / feats. */\nexport interface BounceOddsOptions {\n /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */\n minimumDieRoll?: number;\n /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */\n rerollDamageDice?: number;\n}\n\n/** Binomial coefficient C(n, k), 0 for out-of-range k. */\nfunction binom(n: number, k: number): number {\n if (k < 0 || k > n) return 0;\n let result = 1;\n for (let i = 0; i < k; i++) result = (result * (n - i)) / (i + 1);\n return result;\n}\n\n/**\n * Exact P(all K dice show distinct values) for a die with `uniformCount`\n * ordinary faces (each probability `1/faces`) plus one optional heavy face whose\n * probability is `heavyWeight` (used for the Elemental-Adept collapse; pass 0\n * for a plain die). Uses the elementary symmetric polynomial e_K over the face\n * probabilities: P(all distinct) = K! · e_K.\n */\nfunction pAllDistinct(\n dice: number,\n faces: number,\n uniformCount: number,\n heavyWeight: number\n): number {\n const light = 1 / faces;\n // e_K = (choose K distinct light faces) + (heavy face + K-1 light faces).\n const eK =\n binom(uniformCount, dice) * Math.pow(light, dice) +\n heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1);\n let kFactorial = 1;\n for (let i = 2; i <= dice; i++) kFactorial *= i;\n return kFactorial * eK;\n}\n\n/** Exact P(at least one duplicate) among `dice` dice, honoring Elemental Adept. */\nfunction pMatch(dice: number, faces: number, minimumDieRoll: number): number {\n if (dice <= 1) return 0;\n if (dice > faces) return 1;\n\n if (minimumDieRoll >= 2) {\n // Rolls 1..minimumDieRoll collapse onto the value `minimumDieRoll`, giving it\n // weight minimumDieRoll/faces; the faces above it stay uniform at 1/faces.\n const uniformCount = faces - minimumDieRoll; // values minimumDieRoll+1 .. faces\n const effectiveValues = uniformCount + 1; // + the collapsed value\n if (dice > effectiveValues) return 1;\n const heavyWeight = minimumDieRoll / faces;\n const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight);\n return Math.min(1, Math.max(0, 1 - distinct));\n }\n\n // Plain die: P(all distinct) = falling_factorial(faces, dice) / faces^dice.\n let pDistinct = 1;\n for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces;\n return 1 - pDistinct;\n}\n\n/**\n * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring\n * Elemental Adept and Empowered Spell. Returns a probability in [0, 1].\n *\n * @param diceCount Number of dice rolled.\n * @param dieFaces Faces per die (e.g. 8 for d8).\n * @param options Optional metamagic / feat modifiers.\n */\nexport function calculateBounceOdds(\n diceCount: number,\n dieFaces: number,\n options?: BounceOddsOptions\n): number {\n if (diceCount <= 1) return 0;\n if (diceCount > dieFaces) return 1; // pigeonhole\n\n const minimumDieRoll = options?.minimumDieRoll ?? 0;\n const rerollDamageDice = options?.rerollDamageDice ?? 0;\n\n const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll);\n\n // Without Empowered Spell we're done.\n const rerollCount = Math.min(rerollDamageDice, diceCount);\n if (rerollCount <= 0) return pMatchFirst;\n\n // Empowered Spell: reroll `rerollCount` non-matching dice once. Model the\n // second chance as (a rerolled die matching one of the kept dice) OR (the\n // rerolled dice matching among themselves).\n const pNoMatchFirst = 1 - pMatchFirst;\n const keptDice = diceCount - rerollCount;\n const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces;\n\n // With no kept dice, a rerolled die vacuously \"misses\" all of them (prob 1), so\n // the only way to match is among the rerolled dice themselves (pRerolledMatch below).\n const pRerollDieMissesAll =\n keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1;\n const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll;\n const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0;\n const pMatchAfterReroll = Math.min(\n 1,\n pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches)\n );\n\n return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll);\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n"]} \ No newline at end of file diff --git a/dist/index.d.cts b/dist/index.d.cts deleted file mode 100644 index 2ab6b71..0000000 --- a/dist/index.d.cts +++ /dev/null @@ -1,111 +0,0 @@ -import { P as PMF } from './pmf-D5VRghZI.cjs'; -export { A as ALL_OUTCOME_TYPES, B as Bin, C as CritConfig, D as DamageDistribution, l as DiceQuery, E as EPS, L as LRUCache, M as MISS_NONE_OUTCOME, d as OUTCOME_DISPLAY_ORDER, O as OutcomeLabelMap, m as OutcomeSnapshot, a as OutcomeType, b as RollType, R as Rounding, S as Snapshot, c as critProbability, o as onAnyHit, e as onCritOnly, f as onHitOnly, h as onMissDamageOnly, g as onMissOnly, k as onPotentCantripOnly, j as onSaveFailOnly, i as onSaveHalfOnly, p as pmfCache, s as sortOutcomes } from './pmf-D5VRghZI.cjs'; - -/** - * Bounce odds — the "birthday problem" for bouncing damage dice (e.g. Chromatic - * Orb): the probability that at least two of K dice with S faces show the same - * value, which is what lets the spell jump to another target. - * - * Accounts for two modifiers: - * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are - * bumped up to it, collapsing the low faces onto a single heavier value. - * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be - * rerolled once, giving a second chance at a match. - * - * The base and Elemental-Adept cases are computed exactly (see - * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered - * on the exact base match probability. - */ -/** Options that modify bounce odds via metamagic / feats. */ -interface BounceOddsOptions { - /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */ - minimumDieRoll?: number; - /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */ - rerollDamageDice?: number; -} -/** - * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring - * Elemental Adept and Empowered Spell. Returns a probability in [0, 1]. - * - * @param diceCount Number of dice rolled. - * @param dieFaces Faces per die (e.g. 8 for d8). - * @param options Optional metamagic / feat modifiers. - */ -declare function calculateBounceOdds(diceCount: number, dieFaces: number, options?: BounceOddsOptions): number; - -/** - * Error thrown when a dice expression cannot be parsed. - * - * Extends the built-in {@link Error}, so existing `catch (e)` / message checks - * continue to work, while callers can now narrow with `instanceof DiceParseError`. - * - * @example - * try { - * parse("d6@3"); - * } catch (e) { - * if (e instanceof DiceParseError) { - * // e.expression === "d6@3" - * } - * } - */ -declare class DiceParseError extends Error { - /** The original expression that failed to parse, when available. */ - readonly expression?: string; - /** The underlying error that triggered this one, when available. */ - readonly cause?: unknown; - constructor(message: string, options?: { - expression?: string; - cause?: unknown; - }); -} - -/** Enable or disable the internal parse cache. */ -declare function setCachingEnabled(enabled: boolean): void; -/** Returns whether the internal parse cache is currently enabled. */ -declare function getCachingEnabled(): boolean; -/** Clears the internal parse cache. */ -declare function clearParserCache(): void; -/** - * Parse a dice expression into a PMF. - * - * - Expression is case-insensitive and ignores spaces. - */ -declare function parse(expression: string, n?: number): PMF; - -/** A labeled mixture builder that preserves provenance in Bin.count. */ -declare class Mixture { - private readonly totals; - private readonly labelMass; - private readonly eps; - constructor(eps?: number); - /** Remove all accumulated state. */ - clear(): this; - /** Number of distinct outcome values currently accumulated. */ - size(): number; - /** Whether a label was ever added. */ - hasLabel(label: L): boolean; - /** - * Add a labeled component with a mixture weight. - * Weight can be any positive finite number. Very small contributions are pruned by eps. - */ - add(label: L, pmf: PMF, weight?: number): this; - buildPMF(eps?: number): PMF; - /** - * Produce normalized *per-label* PMFs (labels independent). - * These are unlabeled PMFs built from the raw mass of that label alone. - */ - byOutcome(): Record; - /** - * Mixture weights per label, normalized to sum to 1 over labels that appeared. - * Uses raw mass before per-outcome normalization. - */ - weights(): Record; - toJSON(): { - totals: Array<[number, number]>; - labels: Array<[number, Record]>; - eps: number; - }; - static mix(items: Array<[label: L, pmf: PMF, weight: number]>, eps?: number): PMF; -} - -export { type BounceOddsOptions, DiceParseError, Mixture, PMF, calculateBounceOdds, clearParserCache, getCachingEnabled, parse, setCachingEnabled }; diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 0088156..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { P as PMF } from './pmf-D5VRghZI.js'; -export { A as ALL_OUTCOME_TYPES, B as Bin, C as CritConfig, D as DamageDistribution, l as DiceQuery, E as EPS, L as LRUCache, M as MISS_NONE_OUTCOME, d as OUTCOME_DISPLAY_ORDER, O as OutcomeLabelMap, m as OutcomeSnapshot, a as OutcomeType, b as RollType, R as Rounding, S as Snapshot, c as critProbability, o as onAnyHit, e as onCritOnly, f as onHitOnly, h as onMissDamageOnly, g as onMissOnly, k as onPotentCantripOnly, j as onSaveFailOnly, i as onSaveHalfOnly, p as pmfCache, s as sortOutcomes } from './pmf-D5VRghZI.js'; - -/** - * Bounce odds — the "birthday problem" for bouncing damage dice (e.g. Chromatic - * Orb): the probability that at least two of K dice with S faces show the same - * value, which is what lets the spell jump to another target. - * - * Accounts for two modifiers: - * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are - * bumped up to it, collapsing the low faces onto a single heavier value. - * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be - * rerolled once, giving a second chance at a match. - * - * The base and Elemental-Adept cases are computed exactly (see - * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered - * on the exact base match probability. - */ -/** Options that modify bounce odds via metamagic / feats. */ -interface BounceOddsOptions { - /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */ - minimumDieRoll?: number; - /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */ - rerollDamageDice?: number; -} -/** - * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring - * Elemental Adept and Empowered Spell. Returns a probability in [0, 1]. - * - * @param diceCount Number of dice rolled. - * @param dieFaces Faces per die (e.g. 8 for d8). - * @param options Optional metamagic / feat modifiers. - */ -declare function calculateBounceOdds(diceCount: number, dieFaces: number, options?: BounceOddsOptions): number; - -/** - * Error thrown when a dice expression cannot be parsed. - * - * Extends the built-in {@link Error}, so existing `catch (e)` / message checks - * continue to work, while callers can now narrow with `instanceof DiceParseError`. - * - * @example - * try { - * parse("d6@3"); - * } catch (e) { - * if (e instanceof DiceParseError) { - * // e.expression === "d6@3" - * } - * } - */ -declare class DiceParseError extends Error { - /** The original expression that failed to parse, when available. */ - readonly expression?: string; - /** The underlying error that triggered this one, when available. */ - readonly cause?: unknown; - constructor(message: string, options?: { - expression?: string; - cause?: unknown; - }); -} - -/** Enable or disable the internal parse cache. */ -declare function setCachingEnabled(enabled: boolean): void; -/** Returns whether the internal parse cache is currently enabled. */ -declare function getCachingEnabled(): boolean; -/** Clears the internal parse cache. */ -declare function clearParserCache(): void; -/** - * Parse a dice expression into a PMF. - * - * - Expression is case-insensitive and ignores spaces. - */ -declare function parse(expression: string, n?: number): PMF; - -/** A labeled mixture builder that preserves provenance in Bin.count. */ -declare class Mixture { - private readonly totals; - private readonly labelMass; - private readonly eps; - constructor(eps?: number); - /** Remove all accumulated state. */ - clear(): this; - /** Number of distinct outcome values currently accumulated. */ - size(): number; - /** Whether a label was ever added. */ - hasLabel(label: L): boolean; - /** - * Add a labeled component with a mixture weight. - * Weight can be any positive finite number. Very small contributions are pruned by eps. - */ - add(label: L, pmf: PMF, weight?: number): this; - buildPMF(eps?: number): PMF; - /** - * Produce normalized *per-label* PMFs (labels independent). - * These are unlabeled PMFs built from the raw mass of that label alone. - */ - byOutcome(): Record; - /** - * Mixture weights per label, normalized to sum to 1 over labels that appeared. - * Uses raw mass before per-outcome normalization. - */ - weights(): Record; - toJSON(): { - totals: Array<[number, number]>; - labels: Array<[number, Record]>; - eps: number; - }; - static mix(items: Array<[label: L, pmf: PMF, weight: number]>, eps?: number): PMF; -} - -export { type BounceOddsOptions, DiceParseError, Mixture, PMF, calculateBounceOdds, clearParserCache, getCachingEnabled, parse, setCachingEnabled }; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index c6f02d8..0000000 --- a/dist/index.js +++ /dev/null @@ -1,3601 +0,0 @@ -// src/common/bounce.ts -function binom(n, k) { - if (k < 0 || k > n) return 0; - let result = 1; - for (let i = 0; i < k; i++) result = result * (n - i) / (i + 1); - return result; -} -function pAllDistinct(dice, faces, uniformCount, heavyWeight) { - const light = 1 / faces; - const eK = binom(uniformCount, dice) * Math.pow(light, dice) + heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1); - let kFactorial = 1; - for (let i = 2; i <= dice; i++) kFactorial *= i; - return kFactorial * eK; -} -function pMatch(dice, faces, minimumDieRoll) { - if (dice <= 1) return 0; - if (dice > faces) return 1; - if (minimumDieRoll >= 2) { - const uniformCount = faces - minimumDieRoll; - const effectiveValues = uniformCount + 1; - if (dice > effectiveValues) return 1; - const heavyWeight = minimumDieRoll / faces; - const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight); - return Math.min(1, Math.max(0, 1 - distinct)); - } - let pDistinct = 1; - for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces; - return 1 - pDistinct; -} -function calculateBounceOdds(diceCount, dieFaces, options) { - if (diceCount <= 1) return 0; - if (diceCount > dieFaces) return 1; - const minimumDieRoll = options?.minimumDieRoll ?? 0; - const rerollDamageDice = options?.rerollDamageDice ?? 0; - const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll); - const rerollCount = Math.min(rerollDamageDice, diceCount); - if (rerollCount <= 0) return pMatchFirst; - const pNoMatchFirst = 1 - pMatchFirst; - const keptDice = diceCount - rerollCount; - const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces; - const pRerollDieMissesAll = keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1; - const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll; - const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0; - const pMatchAfterReroll = Math.min( - 1, - pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches) - ); - return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll); -} - -// src/common/errors.ts -var DiceParseError = class _DiceParseError extends Error { - constructor(message, options) { - super(message); - this.name = "DiceParseError"; - this.expression = options?.expression; - this.cause = options?.cause; - Object.setPrototypeOf(this, _DiceParseError.prototype); - } -}; - -// src/common/lru-cache.ts -var LRUCache = class { - constructor(maxSize = 1e3) { - this.maxSize = maxSize; - this.cache = /* @__PURE__ */ new Map(); - } - get(key) { - const value = this.cache.get(key); - if (value === void 0) return void 0; - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - delete(key) { - this.cache.delete(key); - } - set(key, value) { - if (this.cache.size >= this.maxSize && !this.cache.has(key)) { - const oldestKey = this.cache.keys().next().value; - this.cache.delete(oldestKey); - } - this.cache.delete(key); - this.cache.set(key, value); - return this; - } - clear() { - this.cache.clear(); - } - get size() { - return this.cache.size; - } - has(key) { - return this.cache.has(key); - } - keys() { - return this.cache.keys(); - } - values() { - return this.cache.values(); - } -}; - -// src/common/types.ts -var EPS = 1e-12; -function critProbability(critRange, rollType = "flat") { - const base = critRange / 20; - switch (rollType) { - case "advantage": - return 1 - (1 - base) ** 2; - case "elven accuracy": - return 1 - (1 - base) ** 3; - case "disadvantage": - return base ** 2; - case "flat": - default: - return base; - } -} -var MISS_NONE_OUTCOME = "missNone"; -var ALL_OUTCOME_TYPES = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" -]; -var OUTCOME_DISPLAY_ORDER = [ - "crit", - "hit", - "missDamage", - "saveHalf", - "saveFail", - "pc", - "missNone" -]; -function sortOutcomes(outcomes, order = ALL_OUTCOME_TYPES) { - const rank = new Map(order.map((o, i) => [o, i])); - return [...outcomes].sort((a, b) => { - const ra = rank.get(a); - const rb = rank.get(b); - if (ra !== void 0 && rb !== void 0) return ra - rb; - if (ra !== void 0) return -1; - if (rb !== void 0) return 1; - return a.localeCompare(b); - }); -} -var onAnyHit = ["hit", "crit"]; -var onCritOnly = ["crit"]; -var onHitOnly = ["hit"]; -var onMissOnly = ["missNone", "missDamage"]; -var onMissDamageOnly = ["missDamage"]; -var onSaveHalfOnly = ["saveHalf"]; -var onSaveFailOnly = ["saveFail"]; -var onPotentCantripOnly = ["pc"]; - -// src/pmf/query.ts -var _DiceQuery = class _DiceQuery { - constructor(singles, combined, eps = EPS) { - this.singles = Array.isArray(singles) ? singles : [singles]; - if (this.singles.some((s) => s === void 0)) { - throw new Error("DiceQuery contains undefined singles"); - } - this._eps = eps; - this._combinedProvided = combined !== void 0; - if (combined !== void 0) { - this._combined = Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize(); - } - } - /** - * The combined damage distribution of all single PMFs (their convolution), - * normalized to total probability 1. - * - * Computed lazily on first access and cached. Queries that only need - * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, - * {@link DiceQuery.stddev} — never trigger this convolution. - */ - get combined() { - if (this._combined === void 0) { - const c = PMF.convolveMany(this.singles); - this._combined = Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize(); - } - return this._combined; - } - /** - * Returns a new PMF with damage attribution metadata populated. - * - * This method computes attribution on-demand for builder-generated PMFs, - * enabling them to work with damage attribution charts. The `attr` field - * tracks how much damage each outcome type contributes at each damage value. - * - * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) - * - * Performance: Cached after first call. Adds minimal overhead vs `combined`. - * - * @returns PMF with attr field populated for damage attribution charts - * - * @example - * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) - * const query = attack.toQuery() - * const pmf = query.combinedWithAttribution() - * // Now pmf can be used with toDamageAttributionChartSeries() - */ - combinedWithAttribution() { - if (this._combinedWithAttr) { - return this._combinedWithAttr; - } - if (this.singles.every((pmf) => pmf.hasAttribution())) { - this._combinedWithAttr = this.combined; - return this._combinedWithAttr; - } - const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution()); - const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon); - const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize(); - this._combinedWithAttr = normalized; - return normalized; - } - /** - * Per-label `damage value → probability mass` series for the combined, - * attribution-carrying distribution — the provenance core of the stacked - * damage-attribution chart. Convenience for - * `combinedWithAttribution().attributionByValue()`; see - * {@link PMF.attributionByValue}. - */ - attributionByValue() { - return this.combinedWithAttribution().attributionByValue(); - } - /** - * How many of the independent single PMFs can produce the given outcome - * label. Useful for "all of them succeeded" style probabilities where the - * exponent is the number of contributing attacks (see - * {@link DiceQuery.probExactlyK}). - */ - countSinglesWith(label) { - let count = 0; - for (const single of this.singles) { - if (single.hasOutcome(label)) count++; - } - return count; - } - /** - * Returns the expected damage across all possible outcomes. - * - * Example: `query.mean()` → 12.5 - * Use case: "What's my average damage per round?" - */ - mean() { - if (this._combinedProvided) { - let m = 0; - for (const [damageValue, bin] of this.combined) m += damageValue * bin.p; - return m; - } - let totalMean = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - totalMean += Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass; - } - return totalMean; - } - /** - * Returns the variance of the damage distribution. - * - * Example: `query.variance()` → 45.2 - * Use case: "How much does my damage vary from the average?" - * High variance means higher risk/reward. Lower variance means more consistent damage. - */ - variance() { - if (this._combinedProvided) { - const mu = this.mean(); - let v = 0; - for (const [damageValue, bin] of this.combined) { - const dev = damageValue - mu; - v += dev * dev * bin.p; - } - return v; - } - let totalVariance = 0; - for (const single of this.singles) { - const mass = single.mass(); - if (mass <= 0) continue; - if (Math.abs(mass - 1) <= this._eps) { - totalVariance += single.variance(); - } else { - let mu = 0; - for (const [d, b] of single) mu += d * (b.p / mass); - let v = 0; - for (const [d, b] of single) { - const dev = d - mu; - v += dev * dev * (b.p / mass); - } - totalVariance += v; - } - } - return totalVariance; - } - /** - * Returns the standard deviation of the damage distribution. - * - * Example: `query.stdev()` → 6.7 - * Use case: "What's the typical spread around my average damage?" - * Used to determine how consistent the damage is. - */ - stddev() { - return Math.sqrt(this.variance()); - } - /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ - stdev() { - return this.stddev(); - } - /** - * Returns the Cumulative Distribution Function. - */ - cdf(x) { - return this.probTotalAtMost(x); - } - /** - * Returns the probability of dealing X damage or less. - * In statistics, this is called the cumulative distribution function (CDF). - * Example: `query.cdf(20)` → 0.75 - * Use case: "What's the chance I deal 20 damage or less?" - */ - probTotalAtMost(x) { - let cumulativeProbability = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue <= x) { - cumulativeProbability += probabilityBin.p; - } - } - return cumulativeProbability; - } - /** - * Returns the Complementary Cumulative Distribution Function. - */ - ccdf(x) { - return this.probTotalAtLeast(x); - } - /** - * Returns the probability of dealing at least X damage. - * - * Example: `query.probTotalAtLeast(25)` → 0.35 - * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" - */ - probTotalAtLeast(threshold) { - let probabilitySum = 0; - for (const [damageValue, probabilityBin] of this.combined) { - if (damageValue >= threshold) { - probabilitySum += probabilityBin.p; - } - } - return probabilitySum; - } - /** - * Returns damage values at specific percentiles. - * - * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] - * Use case: "What are my 25th, 50th, and 75th percentile damage values?" - */ - percentiles(percentileValues) { - const sortedDamageValues = this.combined.support(); - if (sortedDamageValues.length === 0) return percentileValues.map(() => 0); - const cumulativeProbabilities = []; - let runningProbabilitySum = 0; - for (const damageValue of sortedDamageValues) { - runningProbabilitySum += this.combined.map.get(damageValue).p; - cumulativeProbabilities.push(runningProbabilitySum); - } - return percentileValues.map((targetPercentile) => { - let leftBound = 0; - let rightBound = cumulativeProbabilities.length - 1; - while (leftBound <= rightBound) { - const middleIndex = Math.floor((leftBound + rightBound) / 2); - if (cumulativeProbabilities[middleIndex] >= targetPercentile) { - rightBound = middleIndex - 1; - } else { - leftBound = middleIndex + 1; - } - } - return leftBound < sortedDamageValues.length ? sortedDamageValues[leftBound] : sortedDamageValues[sortedDamageValues.length - 1]; - }); - } - /** - * Returns the minimum possible damage. - * - * Example: `query.min()` → 0 - * Use case: "What's the worst-case damage if everything misses?" - */ - min() { - return this.combined.min(); - } - /** - * Returns the maximum possible damage. - * - * Example: `query.max()` → 56 - * Use case: "What's the best-case damage if everything crits and rolls max?" - */ - max() { - return this.combined.max(); - } - singleProb(diceIndex, label) { - const single = this.singles[diceIndex]; - let probabilitySum = 0; - for (const [, probabilityBin] of single) { - probabilitySum += probabilityBin.count[label] || 0; - } - const mass = single.mass(); - return mass > 0 ? probabilitySum / mass : 0; - } - /** - * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it - * carries ANY of `labels`", over the n independent singles. - * - * Each single's per-event success probability is the Poisson-binomial - * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), - * computed exactly once. The binomial DP then runs once to produce the whole - * distribution, so the array-label paths of probExactlyK / probAtLeastK / - * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and - * re-running the DP per requested k. - */ - countDistribution(labels) { - const n = this.singles.length; - const successProbabilities = this.singles.map( - (single) => new _DiceQuery([single]).probabilityOf(labels) - ); - const dist = new Array(n + 1).fill(0); - dist[0] = 1; - for (const successProb of successProbabilities) { - for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) { - dist[outcomeCount] = dist[outcomeCount] * (1 - successProb) + dist[outcomeCount - 1] * successProb; - } - dist[0] *= 1 - successProb; - } - return dist; - } - probAtLeastK(labels, k) { - const L = Array.isArray(labels) ? [...new Set(labels)] : [labels]; - const n = this.singles.length; - if (k <= 0) return 1; - if (k > n) return 0; - const dist = this.countDistribution(L); - let tail = 0; - for (let i = k; i <= n; i++) { - tail += dist[i]; - } - if (tail < 0) return 0; - if (tail > 1) return 1; - return tail; - } - /** - * Returns the probability that at least one attack has the specified outcome(s). - * - This is the complement of probAtMostK(labels, 0) - * - * Examples: - * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) - * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) - * - * Use cases: - * - "What's the chance at least one of my attacks connects?" - * - * Note: - * - * - You have to pass in an array of labels to avoid double-counting if you are - * using multiple labels. You cannot just add them. - */ - probAtLeastOne(labels) { - if (typeof labels === "string") { - labels = [labels]; - } - let productOfNonOccurrence = 1; - for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) { - let combinedProbability = 0; - for (const label of labels) { - combinedProbability += this.singleProb(diceIndex, label); - } - if (combinedProbability < 0) combinedProbability = 0; - else if (combinedProbability > 1) combinedProbability = 1; - productOfNonOccurrence *= 1 - combinedProbability; - } - const result = 1 - productOfNonOccurrence; - return result < 0 ? 0 : result > 1 ? 1 : result; - } - /** - * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. - * - * Uses dynamic programming to efficiently calculate the probability distribution - * of how many attacks will have the specified outcome, accounting for different - * success probabilities across individual attacks. - * - * Example: For 3 attacks with 50% hit chance each, returns: - * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] - * - * @param label - The outcome type to count - * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) - * @returns Array where index K contains P(exactly K attacks have the label) - */ - computeBinomialProbabilities(label, maxK) { - const individualProbabilities = this.singles.map( - (_, diceIndex) => this.singleProb(diceIndex, label) - ); - const binomialProbs = new Array(maxK + 1).fill(0); - binomialProbs[0] = 1; - for (const singleProbability of individualProbabilities) { - for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) { - binomialProbs[outcomeCount] = binomialProbs[outcomeCount] * (1 - singleProbability) + binomialProbs[outcomeCount - 1] * singleProbability; - } - binomialProbs[0] *= 1 - singleProbability; - } - return binomialProbs; - } - /** - * Returns the probability that exactly K attacks result in the specified outcome(s). - * - * Single label examples: - * - probExactlyK('hit', 2) = probability exactly 2 attacks hit - * - probExactlyK('crit', 1) = probability exactly 1 attack crits - * - probExactlyK('crit', 0) = probability no attacks crit - * - * Array examples: - * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed - * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds - * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss - * - * Use cases: - * - "What's the chance exactly one of my attacks hits?" - * - "How likely am I to get exactly 2 successes out of 3 attacks?" - * - "What's the probability that exactly half my attacks succeed?" - * - * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. - * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. - */ - probExactlyK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - return probabilityArray[k]; - } - const dist = this.countDistribution(labels); - return k >= 0 && k < dist.length ? dist[k] : 0; - } - /** - * Returns the probability that AT MOST K attacks result in the specified outcome(s). - * - * Single label examples: - * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) - * - probAtMostK('crit', 0) = probability no attacks crit - * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss - * - * Array examples: - * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds - * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) - * - * Use cases: - * - "What's the chance that at most one attack hits?" (rest miss) - * - "How likely am I to have mostly failures?" (at most 1 success) - * - "What's the probability of a really bad turn?" (at most 0 successes) - * - */ - probAtMostK(labels, k) { - if (typeof labels === "string") { - const probabilityArray = this.computeBinomialProbabilities(labels, k); - let cumulativeSum2 = 0; - for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) { - cumulativeSum2 += probabilityArray[outcomeCount]; - } - return cumulativeSum2; - } - const dist = this.countDistribution(labels); - const upper = Math.min(k, dist.length - 1); - let cumulativeSum = 0; - for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) { - cumulativeSum += dist[outcomeCount]; - } - return cumulativeSum; - } - /** - * Returns the expected damage attributed to specific outcome types. - * - * Single label examples: - * - expectedDamageFrom('hit') = expected damage from hit components - * - expectedDamageFrom('crit') = expected damage from crit components - * - * Array examples: - * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success - * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses - * - * Use cases: - * - "How much damage do I expect from successful attacks?" - * - "What's the damage contribution from critical hits specifically?" - * - "How much damage comes from miss effects (like save-for-half spells)?" - */ - expectedDamageFrom(labels) { - const wanted = Array.isArray(labels) ? labels : [labels]; - let total = 0; - for (const single of this.singles) { - for (const [dmg, bin] of single) { - let p = 0; - for (const label of wanted) p += bin.count[label] ?? 0; - total += dmg * p; - } - } - return total; - } - /** - * Returns damage statistics for scenarios where AT LEAST ONE attack results in - * the specified outcome(s). - * - * This method answers "What happens when things go reasonably well?" rather than - * "What's the theoretical maximum?" It includes mixed scenarios which are more - * common and tactically relevant than pure scenarios. - * - * Single label examples: - * - damageStatsFrom('hit') = damage range when at least one attack hits - * - damageStatsFrom('crit') = damage range when at least one attack crits - * - * Array examples: - * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds - * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses - * - * Tactical Use Cases: - * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" - * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" - * - "Should I use this risky spell if it has good damage when it works?" - * - "What's my damage potential when something goes right?" (vs pure failure) - * - * Combat Planning Examples: - * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" - * (Much more useful than "You average 50 damage including complete misses") - * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" - * - Resource management: "If I hit anything, I'll likely finish this enemy" - * - * Statistical Note: - * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which - * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. - * - * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an - * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and - * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than - * E[dmg | the label occurs]. For a single attack both are the plain - * conditional figures. Use {@link probAtLeastOne} for the scenario probability. - * - * @example - * // High-level tactical planning - * const successStats = query.damageStatsFrom('hit') - * const successChance = query.probAtLeastOne('hit') - * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) - */ - damageStatsFrom(labels) { - const labelArray = typeof labels === "string" ? [labels] : labels; - let minDamage = Infinity; - let maxDamage = -Infinity; - let totalDamage = 0; - let totalCount = 0; - for (const [damage, probabilityBin] of this.combined) { - let binHasAnyLabel = false; - let binContribution = 0; - for (const label of labelArray) { - const count = probabilityBin.count[label]; - if (count && count > 0) { - binHasAnyLabel = true; - binContribution += count; - } - } - if (damage > 0 && binHasAnyLabel) { - minDamage = Math.min(minDamage, damage); - maxDamage = Math.max(maxDamage, damage); - const weightToUse = labelArray.length === 1 ? binContribution : probabilityBin.p; - totalDamage += damage * weightToUse; - totalCount += weightToUse; - } - } - return { - min: minDamage === Infinity ? 0 : minDamage, - max: maxDamage === -Infinity ? 0 : maxDamage, - avg: totalCount > 0 ? totalDamage / totalCount : 0, - count: totalCount - }; - } - /** - * Returns damage statistics for scenarios where ALL attacks result in the specified - * outcome, calculated by leveraging the pure partition of singles. - * - * This method answers "What's the theoretical best/worst case?" and "What are the - * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. - * - * Examples: - * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) - * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) - * - * UI and Display Use Cases: - * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) - * - "Best case scenario" vs "worst case scenario" analysis - * - Mathematical verification: "Does our hit damage calculation match manual math?" - * - Clean damage type attribution: "How much comes from base hits vs crits?" - * - * Design and Balance Use Cases: - * - Game designers: "What's the damage ceiling if someone gets lucky?" - * - Character optimization: "What's my absolute maximum potential?" - * - Ability comparison: "Which build has higher crit ceiling?" - * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" - * - * Mathematical Use Cases: - * - Validating complex calculations against simple manual math - * - Understanding damage component contributions in isolation - * - Separating luck (crit variance) from consistency (hit variance) - * - Building intuition about damage sources - * - * When to Use This vs damageStatsFrom(): - * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons - * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios - * - * Statistical Note: - * Pure scenarios (all hits, all crits) are rare but represent clear mathematical - * boundaries. These stats help understand the "shape" of your damage potential. - * - * @example - * // UI display logic - * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" - * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" - * - * // vs tactical planning (use damageStatsFrom instead) - * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios - */ - combinedDamageStats(targetLabel) { - const singleStats = this.singles.map( - (single) => new _DiceQuery([single]).damageStatsFrom(targetLabel) - ); - if (singleStats.some((stats) => stats.count === 0)) { - return { min: 0, max: 0, avg: 0, count: 0 }; - } - const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0); - const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0); - const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0); - const combinedProb = singleStats.reduce( - (product, stats) => product * stats.count, - 1 - ); - return { - min: combinedMin, - max: combinedMax, - avg: combinedAvg, - count: combinedProb - }; - } - /** - * Returns the probability that at least one attack carries ANY of the - * specified labels (the marginal P(≥1) across the independent attacks). - * - * Examples: - * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) - * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) - * - * Use cases: - * - "What's the chance my resolution includes a success label?" - * - "How likely am I to get any hits or crits across all attacks?" - * - * Note: this must NOT be computed by summing `combined` bin probabilities. A - * single combined damage total is reachable by many outcome combinations and - * a bin can hold several labels at once, so summing `bin.p` over bins that - * contain a label over-counts. The correct marginal is the Poisson-binomial - * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. - */ - probabilityOf(labels) { - return this.probAtLeastOne(labels); - } - /** - * Returns the probability of missing (any type of miss). - * - * Example: `query.missChance()` → 0.04 - * Use case: "What's the chance I miss completely this turn?" - */ - missChance() { - return this.probabilityOf(["missDamage", "missNone"]); - } - /** - * Returns data formatted for plotting damage probability distribution. - * - * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] - * Use case: "I want to visualize my damage distribution in a chart." - */ - toChartSeries() { - return this.combined.support().map((damageValue) => ({ - x: damageValue, - y: this.combined.map.get(damageValue).p - })); - } - /** - * Returns tabular data showing damage values and their probability breakdowns. - * - * Example: `query.toLabeledTable(['hit', 'crit'])` → - * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] - * - * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." - */ - toLabeledTable(labels = []) { - return this.combined.support().map((damageValue) => { - const probabilityBin = this.combined.map.get(damageValue); - const tableRow = { - damage: damageValue, - total: probabilityBin.p - }; - for (const outcomeLabel of labels) { - tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0; - } - return tableRow; - }); - } - /** - * Returns data for stacked charts with unconditional per-label probability mass per damage. - * - * - Each dataset value equals the unconditional probability mass for that label at that damage - * (i.e., `bin.count[label]`). - * - Column sums may be less than the total probability `bin.p` when you omit labels or when - * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. - * - This behavior matches tests that expect raw per-label mass (not proportional scaling). - * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. - * - * @example - * query.toStackedChartData(['hit', 'crit']) - * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} - */ - toStackedChartData(labels = [], epsilon = EPS) { - const damageValues = this.combined.support(); - const datasets = labels.map((outcomeLabel) => ({ - label: outcomeLabel, - data: damageValues.map((dmg) => { - const bin = this.combined.map.get(dmg); - const v = bin ? bin.count[outcomeLabel] || 0 : 0; - return v <= epsilon ? 0 : v; - }) - })); - return { labels: damageValues, datasets }; - } - /** - * Returns pure mathematical data for attribution charts showing outcome contributions. - * - * Automatically discovers all outcome types present in the PMF, applies filtering rules, - * and returns proportional data suitable for stacked visualization. - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and proportional data - * - * @example - * query.toAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} - */ - toAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeCount = bin.count[outcome] || 0; - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - const outcomeProbability = bin.p * outcomeFraction; - return asPercentages ? outcomeProbability * 100 : outcomeProbability; - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for damage attribution charts showing damage contribution - * from each outcome type at each damage value. - * - * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of - * bin.count (probability attribution). - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) - * @returns Pure data structure with support, outcomes, and damage attribution data - * - * @example - * query.toDamageAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} - */ - toDamageAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - if (bin.attr) { - for (const outcomeType in bin.attr) { - if (bin.attr[outcomeType] && bin.attr[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin || !bin.attr) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - const outcomeDamageAttribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) { - if (filterRules(outcomeName, damage)) { - totalDamageAttribution += damageAttr || 0; - } - } - if (totalDamageAttribution === 0) return 0; - const damagePercentage = outcomeDamageAttribution / totalDamageAttribution * 100; - return damagePercentage * bin.p * 100; - } else { - return outcomeDamageAttribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for outcome attribution charts showing which - * attack outcome combinations can produce each damage value. - * - * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks - * outcome combinations - answering "what attack outcomes produced this damage?" - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and outcome combination probabilities - * - * @example - * query.toOutcomeAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} - */ - toOutcomeAttributionChartSeries(options = {}) { - const { - stackOrder = [ - "missNone", - "missDamage", - "saveFail", - "saveHalf", - "pc", - "hit", - "crit" - ], - filterRules = (outcome, damage) => !(outcome === "missNone" && damage !== 0), - asPercentages = true - } = options; - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], outcomes: [], data: {} }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - const allOutcomeTypes = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const outcomeType in bin.count) { - if (bin.count[outcomeType] && bin.count[outcomeType] > 0) { - allOutcomeTypes.add(outcomeType); - } - } - } - const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => { - const indexA = stackOrder.indexOf(a); - const indexB = stackOrder.indexOf(b); - if (indexA >= 0 && indexB >= 0) return indexA - indexB; - if (indexA >= 0) return -1; - if (indexB >= 0) return 1; - return a.localeCompare(b); - }); - if (existingOutcomes.length === 0) { - return { support, outcomes: [], data: {} }; - } - const data = {}; - for (const outcome of existingOutcomes) { - data[outcome] = support.map((damage) => { - const bin = this.combined.map.get(damage); - if (!bin) return 0; - if (!filterRules(outcome, damage)) { - return 0; - } - if (outcome === "missNone") { - const outcomeCount = bin.count[outcome] || 0; - if (outcomeCount === 0) return 0; - if (asPercentages) { - let totalChartableCount = 0; - for (const [outcomeName, count] of Object.entries(bin.count)) { - if (filterRules(outcomeName, damage)) { - totalChartableCount += count || 0; - } - } - if (totalChartableCount === 0) return 0; - const outcomeFraction = outcomeCount / totalChartableCount; - return outcomeFraction * bin.p * 100; - } else { - return outcomeCount; - } - } - if (!bin.attr) return 0; - const outcomeDamageContribution = bin.attr[outcome] || 0; - if (asPercentages) { - let totalDamageAttribution = 0; - for (const [, damageAttr] of Object.entries(bin.attr)) { - totalDamageAttribution += damageAttr || 0; - } - if (totalDamageAttribution === 0) return 0; - const outcomeFraction = outcomeDamageContribution / totalDamageAttribution; - return outcomeFraction * bin.p * 100; - } else { - return outcomeDamageContribution; - } - }); - } - return { - support, - outcomes: existingOutcomes, - data - }; - } - /** - * Returns pure mathematical data for cumulative distribution function (CDF). - * Shows P(X ≤ x) - the probability of getting at most x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and cumulative probabilities - * - * @example - * query.toCDFSeries() - * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} - */ - toCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const cdfData = []; - for (const damage of support) { - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - cdfData.push( - asPercentages ? cumulativeProbability * 100 : cumulativeProbability - ); - } - return { - support, - data: cdfData - }; - } - /** - * Returns pure mathematical data for complementary cumulative distribution function (CCDF). - * Shows P(X ≥ x) - the probability of getting at least x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and complementary cumulative probabilities - * - * @example - * query.toCCDFSeries() - * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} - */ - toCCDFSeries(asPercentages = true) { - const originalSupport = this.combined.support(); - if (originalSupport.length === 0) { - return { support: [], data: [] }; - } - const minDamage = Math.min(...originalSupport); - const maxDamage = Math.max(...originalSupport); - const support = Array.from( - { length: maxDamage - minDamage + 1 }, - (_, i) => minDamage + i - ); - let cumulativeProbability = 0; - const ccdfData = []; - for (const damage of support) { - const ccdf = 1 - cumulativeProbability; - ccdfData.push(asPercentages ? ccdf * 100 : ccdf); - const bin = this.combined.map.get(damage); - if (bin) { - cumulativeProbability += bin.p; - } - } - return { - support, - data: ccdfData - }; - } - /* - Statistics snapshot of the query. - */ - /** Probability of doing strictly more than threshold damage (default >0). */ - probDamageGreaterThan(threshold = 0) { - let acc = 0; - for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p; - return acc; - } - /** All outcome keys actually present (typed & ordered if you pass an order). */ - outcomeKeys(order) { - const found = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) - if (bin.count[k] && bin.count[k] > 0) found.add(k); - } - if (found.size === 0) - ["hit", "crit", "missNone"].forEach((k) => found.add(k)); - const keys = Array.from(found).filter( - (k) => order?.includes(k) ?? true - ); - if (order && order.length) - keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999)); - return keys; - } - /** Total probability per outcome across the PMF. */ - outcomeTotals(outcomes = this.outcomeKeys()) { - const totals = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => totals.set(o, 0)); - for (const [, row] of this.combined.map) { - for (const o of outcomes) { - const p = row.count[o] || 0; - totals.set(o, (totals.get(o) || 0) + p); - } - } - return totals; - } - /** Conditional damage range per outcome (min/avg/max of X | outcome). */ - outcomeDamageRanges(outcomes = this.outcomeKeys()) { - const table = this.toLabeledTable(outcomes); - const ranges = /* @__PURE__ */ new Map(); - outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 })); - for (const row of table) { - const dmg = row.damage; - for (const o of outcomes) { - const p = row[o] || 0; - if (p > 0) { - const r = ranges.get(o); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - } - const out = /* @__PURE__ */ new Map(); - for (const o of outcomes) { - const r = ranges.get(o); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 }); - } - return out; - } - /** - * Snapshot of the distribution in the exact shape the UI consumes. - * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) - * - damageRange is conditional on the outcome occurring - * - * The outcome probabilities use the correct Poisson-binomial marginals - * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), - * so they are always valid probabilities in [0,1]. - * - * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from - * the combined PMF's `count`, which the convolution accumulates as an EXPECTED - * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] - * rather than a clean conditional expectation. It is correct for a single - * attack. - */ - snapshot(order) { - const discovered = /* @__PURE__ */ new Set(); - for (const [, bin] of this.combined.map) { - for (const k in bin.count) { - if (bin.count[k] && bin.count[k] > 0) discovered.add(k); - } - } - if (discovered.size === 0) { - for (const k of _DiceQuery.DEFAULT_OUTCOMES) discovered.add(k); - } - let outcomes = Array.from(discovered); - if (order && order.length) { - const inOrder = new Set(order); - outcomes = outcomes.filter((k) => inOrder.has(k)); - const rank = new Map(order.map((k, i) => [k, i])); - outcomes.sort( - (a, b) => (rank.get(a) ?? 999) - (rank.get(b) ?? 999) - ); - } - const rows = this.toLabeledTable(outcomes); - const rangeAcc = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - rangeAcc.set(ot, { sum: 0, mass: 0 }); - } - for (const row of rows) { - const dmg = row.damage; - for (const ot of outcomes) { - const p = row[ot] || 0; - if (p <= 0) continue; - const r = rangeAcc.get(ot); - r.sum += dmg * p; - r.mass += p; - if (r.min === void 0 || dmg < r.min) r.min = dmg; - if (r.max === void 0 || dmg > r.max) r.max = dmg; - } - } - const n = this.singles.length; - const outcomeMap = /* @__PURE__ */ new Map(); - for (const ot of outcomes) { - const r = rangeAcc.get(ot); - const avg = r.mass > 0 ? r.sum / r.mass : 0; - outcomeMap.set(ot, { - atLeastOneProbability: this.probAtLeastOne(ot), - allProbability: this.probAtLeastK(ot, n), - damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 } - }); - } - const averageDPR = this.mean(); - let damageChance = 0; - for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p; - const { support, data } = this.toCDFSeries(false); - const quantile = (p) => { - if (support.length === 0) return 0; - for (let i = 0; i < support.length; i++) - if (data[i] >= p) return support[i]; - return support[support.length - 1]; - }; - const percentiles = { - p25: quantile(0.25), - p50: quantile(0.5), - p75: quantile(0.75) - }; - return { averageDPR, damageChance, percentiles, outcomes: outcomeMap }; - } - /** - * PMF Transformation Methods - * - * These methods provide a fluent API for transforming dice queries by wrapping - * the underlying PMF transformation methods. All operations work on the combined - * PMF and return new DiceQuery instances. - */ - /** - * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). - * - * @returns New DiceQuery with normalized combined PMF - */ - normalize() { - return new _DiceQuery([this.combined.normalize()]); - } - /** - * Returns a new DiceQuery with low-probability outcomes removed. - * - * @param eps Minimum probability threshold (defaults to PMF epsilon) - * @param keepFinalBin Whether to keep the highest damage bin regardless of probability - * @returns New DiceQuery with compacted combined PMF - */ - compact(eps, keepFinalBin) { - return new _DiceQuery([this.combined.compact(eps, keepFinalBin)]); - } - /** - * Returns a new DiceQuery with an additional scaled branch added. - * Useful for conditional outcomes like "30% chance of opportunity attack". - * - * @param branch DiceQuery to add as a scaled branch - * @param probability Probability of the branch occurring (0-1) - * @returns New DiceQuery combining this query with the scaled branch - * - * @example - * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); - * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); - */ - addScaled(branch, probability) { - return new _DiceQuery([ - this.combined.addScaled(branch.combined, probability) - ]); - } - /** - * Returns a new DiceQuery with all probabilities scaled by a factor. - * Used for conditional scenarios where the entire outcome has reduced probability. - * - * @param factor Scaling factor for probabilities - * @returns New DiceQuery with scaled probabilities - * - * @example - * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario - */ - scaleMass(factor) { - return new _DiceQuery([this.combined.scaleMass(factor)]); - } - totalMass() { - return this.combined.mass(); - } - /** - * Returns a new DiceQuery with damage values transformed by a function. - * Useful for applying modifiers, resistances, or other damage transformations. - * - * @param damageTransformFunction Function to transform each damage value - * @returns New DiceQuery with transformed damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage - * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage - */ - mapDamage(damageTransformFunction) { - return new _DiceQuery([this.combined.mapDamage(damageTransformFunction)]); - } - /** - * Returns a new DiceQuery with damage values scaled by a factor. - * Convenient wrapper around mapDamage for multiplicative scaling. - * - * @param factor Scaling factor for damage values - * @param rounding Rounding method: "floor" (default), "round", or "ceil" - * @returns New DiceQuery with scaled damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const doubled = baseAttack.scaleDamage(2); // Double damage - * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded - */ - scaleDamage(factor, rounding = "floor") { - return new _DiceQuery([this.combined.scaleDamage(factor, rounding)]); - } - /** - * Returns a new DiceQuery combining this query with another via convolution. - * Equivalent to rolling both queries independently and adding results. - * It is important to use this rather than combing()ing the PMFs directly! - * This method maintains the provenance of the PMFs which is needed for damage attribution. - * Combining the .combined PMFs directly is still valid for DPR calculations but - * is not statistically sound for queries. - * - * @param other DiceQuery to combine with - * @param eps Optional epsilon for precision control - * @returns New DiceQuery representing the combined outcome - * - * @example - * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); - * const bothAttacks = mainAttack.convolve(bonusAttack); - */ - convolve(other) { - const singles = [...this.singles, ...other.singles]; - return new _DiceQuery(singles); - } - /** - * First-success split over an ordered list of DISTINCT single-swing PMFs. - * Each PMF may have different success/subset probabilities (from labels). - * - * successOutcome: e.g., ["success"] or ["hit", "crit"] - * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success - * - * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] - */ - firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) { - const pmfs = this.singles; - if (!pmfs.length) { - throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty"); - } - const toArr = (x) => Array.isArray(x) ? x : [x]; - const clamp01 = (x) => Math.max(0, Math.min(1, x)); - const tol = Math.max(eps, 8 * Number.EPSILON); - const per = pmfs.map((pmf) => { - const dq = new _DiceQuery([pmf]); - const pS = dq.probAtLeastOne(toArr(successOutcome)); - const pB = dq.probAtLeastOne(toArr(subsetOutcome)); - if (pB - pS > eps) { - throw new Error( - "firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset \u2286 success." - ); - } - return { pS, pB }; - }); - let missSoFar = 1; - let pFirstSubset = 0; - let pFirstNonSubset = 0; - let pNone = 1; - for (const { pS, pB } of per) { - pFirstSubset += missSoFar * pB; - pFirstNonSubset += missSoFar * (pS - pB); - const miss = 1 - pS; - missSoFar *= miss; - pNone *= miss; - } - const pAny = 1 - pNone; - const a = clamp01(pFirstNonSubset); - const b = clamp01(pFirstSubset); - const any = clamp01(pAny); - const none = clamp01(pNone); - if (Math.abs(a + b - any) > tol * Math.max(1, any)) { - throw new Error( - `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${a + b}, pAny=${any}` - ); - } - return [a, b, any, none]; - } -}; -_DiceQuery.DEFAULT_OUTCOMES = [ - "hit", - "crit", - "missNone" -]; -var DiceQuery = _DiceQuery; -var pmfCache = new LRUCache(1e3); -var _PMF = class _PMF { - constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) { - this.map = map; - this.epsilon = epsilon; - this.normalized = normalized; - this.identifier = identifier; - this._preservedProvenance = _preservedProvenance; - } - static empty(epsilon = EPS, identifier = "empty") { - return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier); - } - // This has a single bin at value 0, mass of 1 - static zero(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { miss: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "zero"); - } - static delta(value, epsilon = EPS) { - return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon); - } - /** - * Point mass at damage 0 tagged with the canonical `missNone` outcome. - * - * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the - * builder's attack-resolution vocabulary. This uses the `missNone` - * {@link OutcomeType} that the attribution charts and outcome stats key on, - * so it is the correct "clean miss / no damage" delta for provenance-aware - * mixtures feeding those consumers. - */ - static missNone(epsilon = EPS) { - const m = /* @__PURE__ */ new Map(); - m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} }); - return new _PMF(m, epsilon, false, "missNone"); - } - // This creates a single bin at value 0, but with weight 0. - static emptyMass() { - return _PMF.zero().scaleMass(0); - } - // Makes PMF iterable over [damage, bin] pairs. - [Symbol.iterator]() { - return this.map[Symbol.iterator](); - } - static clearCache() { - pmfCache.clear(); - } - /** - * Creates a conditional PMF from two branches (success and failure) and a probability. - * This is the core logic for modeling any probabilistic event where there are two - * distinct outcomes. - */ - static branch(successPMF, failurePMF, successProbability) { - let p = successProbability; - if (!Number.isFinite(p)) p = 0; - if (p < 0) p = 0; - if (p > 1) p = 1; - const q = 1 - p; - if (p === 0) return failurePMF.scaleMass(1); - if (p === 1) return successPMF.scaleMass(1); - const eps = successPMF.epsilon ?? failurePMF.epsilon; - const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${successPMF.identifier}*${p.toFixed(6)})`; - const resultMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of failurePMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, q)); - } - for (const [damageValue, bin] of successPMF.map) { - _PMF.mergeInto(resultMap, damageValue, _PMF.scaleBin(bin, p)); - } - return new _PMF(resultMap, eps, false, id); - } - /** - * withProbability() - * - * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). - * - * Think of this as a shortcut for: - * pmf.gate(p, PMF.zero()) - * - * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, - * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. - * - * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. - * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. - * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) - * use query.firstSuccessSplit() to get the exact probabilities. - * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). - * - */ - static withProbability(successPMF, probability) { - return _PMF.branch(successPMF, _PMF.zero(), probability); - } - /** - * gate() - * - * A conditional wrapper around branch() that applies this PMF with probability `p`, - * and applies a provided fallback PMF otherwise. - * - * This is useful for modeling a binary choice between two outcomes: - * - The "success" outcome (this PMF) happens with probability `p`. - * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. - * - * Examples: - * - 25% chance to include an opportunity attack, otherwise nothing: - * attackPMF.gate(0.25, PMF.zero()) - * - * - 50% chance to deal fireball damage, otherwise cone of cold damage: - * fireballPMF.gate(0.5, coneOfColdPMF) - * - * Relationship to other helpers: - * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. - * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). - * - * @param p Probability of applying this PMF (between 0 and 1). - * @param fallback PMF to apply when this PMF is *not* selected. - * @returns A new PMF representing the weighted mixture of this PMF and the fallback. - */ - gate(p, fallback) { - return _PMF.branch(this, fallback, p); - } - /** - * PMF.exclusive() - * - * Builds a single PMF from a set of mutually exclusive weighted outcomes. - * Exactly one of the provided options will occur. - * - * Each option has: - * - A PMF representing its outcome (e.g., damage dice). - * - A weight representing its probability of being selected. - * - * Notes: - * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for floating point rounding. - */ - static exclusive(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight) || weight < -eps) { - throw new Error(`PMF.exclusive: invalid weight ${weight}.`); - } - } - let totalWeight = items.reduce((s, { weight }) => s + weight, 0); - if (Math.abs(totalWeight) <= eps) totalWeight = 0; - if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1; - if (totalWeight > 1 + EPS) { - throw new Error( - `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})` - ); - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (weight > eps) out = out.addScaled(pmf, weight); - } - const leftover = Math.max(0, 1 - totalWeight); - if (leftover > eps) { - out = out.addScaled(_PMF.zero(), leftover); - } - return out; - } - /** - * PMF.mix() - * - * Builds a PMF as a linear combination of input PMFs with the given weights. - * Unlike `exclusive`, this does NOT: - * - enforce that weights sum to 1 - * - add leftover probability to δ0 (PMF.zero()) - * - * Use when outcomes are not mutually exclusive, or for interpolation/blending. - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for skipping tiny weights. - */ - static mix(options, eps = EPS) { - const items = options.map( - (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o - ); - for (const { weight } of items) { - if (!Number.isFinite(weight)) { - throw new Error(`PMF.mix: invalid weight ${weight}.`); - } - } - let out = _PMF.empty(eps); - for (const { pmf, weight } of items) { - if (Math.abs(weight) <= eps) continue; - out = out.addScaled(pmf, weight); - } - return out; - } - /** - * Adds damage attribution metadata to this PMF based on existing count metadata. - * For each bin, sets attr[outcome] = damage × count[outcome]. - * - * This enables damage attribution charts to work with builder-generated PMFs. - * The parser generates attr automatically, but builder PMFs only have count. - * - * @returns New PMF with attr field populated in each bin - */ - /** - * Returns true if this PMF already carries damage attribution metadata. - * - * Only the first positive-damage bin is inspected (parser-generated PMFs - * populate `attr` uniformly), so this is O(1) in practice. - */ - hasAttribution() { - for (const [damage, bin] of this.map) { - if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) { - return true; - } - if (damage > 0) break; - } - return false; - } - withAttribution() { - if (this.hasAttribution()) return this; - const newMap = /* @__PURE__ */ new Map(); - for (const [damage, bin] of this.map) { - const attr = {}; - for (const outcome in bin.count) { - const probability = bin.count[outcome]; - if (probability > 0) { - attr[outcome] = damage * probability; - } - } - newMap.set(damage, { - p: bin.p, - count: { ...bin.count }, - attr: Object.keys(attr).length > 0 ? attr : void 0 - }); - } - return new _PMF( - newMap, - this.epsilon, - this.normalized, - `${this.identifier}~attr` - ); - } - /** - * General-purpose N-way mixture. - * weights: Array of [weight, PMF]. - * - * Example: PMF.mixN([ - * [pMiss, zero], - * [pHit, hitPMF], - * [pCrit, critPMF], - * ]); - */ - static mixN(weights, eps = EPS) { - const filtered = weights.filter(([w]) => w > eps); - if (filtered.length === 0) { - return _PMF.emptyMass(); - } - let acc = null; - let sum = 0; - for (const [w, pmf] of filtered) { - if (acc === null) { - acc = pmf; - sum = w; - } else { - const q = w / (sum + w); - acc = _PMF.branch(pmf, acc, q); - sum += w; - } - } - return acc ?? _PMF.emptyMass(); - } - // This is a convenience method for when we use power - // TODO: It can be smarter in the future, and we can also add it to query - // That way statistics operations on invalid PMFs can throw an error - // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance? - setPreservedProvenance(preserved) { - if (!this._preservedProvenance && preserved) { - throw new Error( - "Preserved provenance is already set to false, cannot fix that" - ); - } - this._preservedProvenance = preserved; - } - preservedProvenance() { - return this._preservedProvenance; - } - getPowerCacheKey(n, eps) { - const id = this.identifier; - let key = `${id}`; - for (let i = 1; i < n; i++) key += `+${id}`; - return `${key}@${eps}`; - } - /** - * Efficiently computes this PMF convolved with itself `n` times. - * Uses exponentiation by squaring to reduce total convolutions. - * n must be a positive integer. - * * - * * NOTE: This folds multiple independent attacks into a single PMF. - * As a result, The power() method causes a loss of data provenance. - * This is ONLY SAFE if you are trying to calculate masses. - * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). - */ - power(n, eps = this.epsilon) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("power(n): n must be a positive integer"); - } - if (n === 1) return this; - const epsilon = eps ?? this.epsilon; - const key = this.getPowerCacheKey(n, epsilon); - { - const cached = pmfCache?.get(key); - if (cached) return cached; - } - let base = this.normalized ? this : this.normalize(); - let result = base; - let exp = n - 1; - while (exp > 0) { - if (exp & 1) { - result = result.convolve(base, epsilon); - } - exp >>= 1; - if (exp > 0) { - base = base.convolve(base, epsilon); - } - } - result.setPreservedProvenance(false); - { - pmfCache?.set(key, result); - } - return result; - } - /* - * Helper for chaining multiple identical attacks - */ - replicate(n) { - if (!Number.isInteger(n) || n <= 0) { - throw new Error("replicate(n): n must be a positive integer"); - } - if (n === 1) return [this]; - return Array.from({ length: n }, () => this); - } - mass() { - if (this._totalMass === void 0) { - let totalProbabilityMass = 0; - for (const { p } of this.map.values()) { - totalProbabilityMass += p; - } - this._totalMass = totalProbabilityMass; - } - return this._totalMass; - } - outcomeMass(outcome) { - let totalProbabilityMass = 0; - for (const { p, count } of this.map.values()) { - totalProbabilityMass += p * (count[outcome] ?? 0); - } - return totalProbabilityMass; - } - // Helper for testing - faceTotal() { - return [...this.map.keys()].reduce((sum, key) => sum + key, 0); - } - normalize() { - if (this.normalized) return this; - const normalizationFactor = this.mass(); - if (normalizationFactor === 0) return this; - const normalizedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const normalizedCount = {}; - for (const labelKey in probabilityBin.count) { - normalizedCount[labelKey] = probabilityBin.count[labelKey] / normalizationFactor; - } - let normalizedAttributes; - if (probabilityBin.attr) { - normalizedAttributes = {}; - for (const labelKey in probabilityBin.attr) { - normalizedAttributes[labelKey] = probabilityBin.attr[labelKey] / normalizationFactor; - } - } - normalizedMap.set(damageValue, { - p: probabilityBin.p / normalizationFactor, - count: normalizedCount, - attr: normalizedAttributes - }); - } - return new _PMF(normalizedMap, this.epsilon, true, this.identifier); - } - /** - * Returns a copy with negligible probabilities removed (p < eps). - * If keepFinalBin is true, the bin with the largest key is always kept, - * even if its probability is below eps. count/attr submaps are still cleaned. - */ - compact(eps = this.epsilon, keepFinalBin = false) { - let maxKey = -Infinity; - if (keepFinalBin) { - for (const key of this.map.keys()) { - if (key > maxKey) maxKey = key; - } - } - const compactedMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - const shouldKeep = probabilityBin.p >= eps || keepFinalBin && damageValue === maxKey; - if (!shouldKeep) continue; - const cleanedBin = _PMF.cloneBin(probabilityBin); - for (const labelKey in cleanedBin.count) { - if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) { - delete cleanedBin.count[labelKey]; - } - } - if (cleanedBin.attr) { - for (const labelKey in cleanedBin.attr) { - if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) { - delete cleanedBin.attr[labelKey]; - } - } - if (Object.keys(cleanedBin.attr).length === 0) { - cleanedBin.attr = void 0; - } - } - compactedMap.set(damageValue, cleanedBin); - } - return new _PMF(compactedMap, eps, this.normalized, this.identifier); - } - // Note: The "support" of a PMF is the set of all non-zero probability outcomes. - // This returns all damage values with non-zero probability, sorted ascending. - support() { - if (this._support === void 0) { - this._support = [...this.map.keys()].sort((a, b) => a - b); - } - return this._support; - } - // Minimum possible damage value. - min() { - if (this._min === void 0) { - const support = this.support(); - this._min = support.length > 0 ? support[0] : 0; - } - return this._min; - } - // Maximum possible damage value. - max() { - if (this._max === void 0) { - const support = this.support(); - this._max = support.length > 0 ? support[support.length - 1] : 0; - } - return this._max; - } - /** - * Returns the expected (mean) damage value. - * Cached for performance since this requires iterating through all bins. - */ - mean() { - if (this._mean === void 0) { - let totalSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - totalSum += damageValue * probabilityBin.p; - } - this._mean = totalSum; - } - return this._mean; - } - /** - * Returns the variance of the damage distribution. - * Cached for performance since this requires mean calculation plus iteration. - */ - variance() { - if (this._variance === void 0) { - const meanValue = this.mean(); - let varianceSum = 0; - for (const [damageValue, probabilityBin] of this.map) { - const deviationFromMean = damageValue - meanValue; - varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p; - } - this._variance = varianceSum; - } - return this._variance; - } - /** - * Returns the standard deviation of the damage distribution. - */ - stdev() { - if (this._stdev === void 0) { - this._stdev = Math.sqrt(this.variance()); - } - return this._stdev; - } - /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ - static cloneBin(bin) { - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ - static scaleBin(bin, factor) { - const count = {}; - for (const k in bin.count) { - count[k] = bin.count[k] * factor; - } - let attr; - if (bin.attr) { - attr = {}; - for (const k in bin.attr) { - attr[k] = bin.attr[k] * factor; - } - } - return { p: bin.p * factor, count, attr }; - } - static mergeInto(destinationMap, damageValue, binToAdd) { - const existingBin = destinationMap.get(damageValue); - if (!existingBin) { - destinationMap.set(damageValue, _PMF.cloneBin(binToAdd)); - return; - } - existingBin.p += binToAdd.p; - for (const labelKey in binToAdd.count) { - existingBin.count[labelKey] = (existingBin.count[labelKey] || 0) + binToAdd.count[labelKey]; - } - if (binToAdd.attr) { - if (!existingBin.attr) { - existingBin.attr = {}; - } - for (const labelKey in binToAdd.attr) { - existingBin.attr[labelKey] = (existingBin.attr[labelKey] || 0) + binToAdd.attr[labelKey]; - } - } - } - // Convenience method - add(other) { - return this.addScaled(other, 1); - } - /** - * Returns a new PMF with a scaled branch added to this one. - * The branch PMF is scaled by the given probability before merging - * This will be very useful for conditional effects and for being - * able to model "I can probably have this opportunity attack 40% of rounds" - * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes - */ - addScaled(branch, probability) { - if (probability === 0) return this; - const resultMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of this.map) { - resultMap.set(dmg, _PMF.cloneBin(bin)); - } - for (const [damageValue, probabilityBin] of branch.map) { - _PMF.mergeInto( - resultMap, - damageValue, - _PMF.scaleBin(probabilityBin, probability) - ); - } - return new _PMF( - resultMap, - this.epsilon, - false, - `${this.identifier}+scaled(${branch.identifier},${probability})` - ); - } - /** - * Redistributes probability mass to model an effect that only occurs with - * probability `frequency` — a conditional attack, an on-hit rider, or a - * sub-one AoE target fraction. - * - * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, - * per-label `count`, AND per-label `attr` — and the freed mass is moved into - * the miss bin at damage 0, tagged with the canonical `missNone` outcome. - * Total probability mass is preserved. - * - * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage - * attribution (`attr`) intact, so a frequency-scaled PMF still renders - * correctly in the damage-attribution charts. - * - * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` - * collapses all mass into the miss bin. The miss outcome is assumed to be - * encoded at damage value 0. - * - * @param frequency Probability in [0, 1] that the effect occurs. - */ - applyHitFrequency(frequency) { - if (!Number.isFinite(frequency) || frequency >= 1) return this; - const freq = Math.max(0, frequency); - const pMiss = this.pAt(0); - const pHit = 1 - pMiss; - const newMissMass = pMiss + (1 - freq) * pHit; - const newMap = /* @__PURE__ */ new Map(); - newMap.set(0, { - p: newMissMass, - count: { [MISS_NONE_OUTCOME]: newMissMass }, - attr: {} - }); - for (const [damage, bin] of this.map) { - if (damage <= 0) continue; - newMap.set(damage, _PMF.scaleBin(bin, freq)); - } - return new _PMF( - newMap, - this.epsilon, - false, - `freq(${this.identifier},${freq})` - ); - } - scaleMass(factor) { - if (factor === 1) return this; - const scaledMap = /* @__PURE__ */ new Map(); - for (const [damageValue, probabilityBin] of this.map) { - scaledMap.set(damageValue, _PMF.scaleBin(probabilityBin, factor)); - } - return new _PMF( - scaledMap, - this.epsilon, - false, - `scale(${this.identifier},${factor})` - ); - } - mapDamage(damageTransformFunction) { - const transformedMap = /* @__PURE__ */ new Map(); - for (const [originalDamage, probabilityBin] of this.map) { - const transformedDamage = damageTransformFunction(originalDamage); - _PMF.mergeInto( - transformedMap, - transformedDamage, - _PMF.cloneBin(probabilityBin) - ); - } - return new _PMF( - transformedMap, - this.epsilon, - this.normalized, - `map(${this.identifier})` - ); - } - scaleDamage(factor, rounding = "floor") { - const roundFunction = rounding === "round" ? Math.round : rounding === "ceil" ? Math.ceil : Math.floor; - return this.mapDamage((damageValue) => roundFunction(damageValue * factor)); - } - getPMFCombineCacheKey(p1, p2, eps, raw) { - const [id1, id2] = [p1.identifier, p2.identifier].sort(); - return `v4:${raw ? "RAW" : "N"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`; - } - /** - * A small content fingerprint (mass + bin count + face sum) so convolution - * cache keys change if the underlying numbers do. Memoized because a PMF is - * immutable once constructed — this avoids re-summing every key on each - * convolve() call (including cache hits). - */ - fingerprint() { - if (this._fingerprint === void 0) { - let faceSum = 0; - for (const k of this.map.keys()) faceSum += k; - this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`; - } - return this._fingerprint; - } - convolve(other, eps, raw = false) { - const epsilon = eps ?? this.epsilon; - const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize(); - const A0 = norm(this); - const B0 = norm(other); - const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0]; - const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw); - const cached = pmfCache?.get(cacheKey); - if (cached) return cached; - const combinedMap = /* @__PURE__ */ new Map(); - for (const [aVal, aBin] of A.map) { - const ap = aBin.p; - const aCount = aBin.count; - const aAttr = aBin.attr; - for (const [bVal, bBin] of B.map) { - const bp = bBin.p; - const dmg = aVal + bVal; - let dest = combinedMap.get(dmg); - if (dest === void 0) { - dest = { p: 0, count: {} }; - combinedMap.set(dmg, dest); - } - dest.p += ap * bp; - const dc = dest.count; - for (const k in aCount) dc[k] = (dc[k] || 0) + aCount[k] * bp; - for (const k in bBin.count) - dc[k] = (dc[k] || 0) + bBin.count[k] * ap; - if (aAttr || bBin.attr) { - let da = dest.attr; - if (da === void 0) { - da = {}; - dest.attr = da; - } - if (aAttr) - for (const k in aAttr) da[k] = (da[k] || 0) + aAttr[k] * bp; - if (bBin.attr) - for (const k in bBin.attr) - da[k] = (da[k] || 0) + bBin.attr[k] * ap; - } - } - } - let result = new _PMF( - combinedMap, - epsilon, - !raw, - `${A.identifier}${raw ? "*" : "+"}${B.identifier}` - ); - const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1); - const mGot = result.mass(); - if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) { - result = result.scaleMass(mExp / mGot); - } - if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon) - result = result.normalize(); - pmfCache?.set(cacheKey, result); - return result; - } - // 3) Nice wrapper so you can call pmf.combineRaw(other) - combineRaw(other, eps) { - return this.convolve(other, eps, true); - } - // Reduce a list of PMFs by left-folding convolve() with the given eps - static reduceConvolveLeft(pmfList, eps) { - let result = pmfList[0]; - for (let i = 1; i < pmfList.length; i++) { - result = result.convolve(pmfList[i], eps); - } - return result; - } - /** - * Convolves multiple PMFs using linear convolution with automatic caching. - * Uses a left-to-right accumulation approach for maximum cache reuse. - * Each convolve() call automatically uses the convolution cache for performance. - * - * This linear approach provides better cache hits than pairwise because: - * - Intermediate results are more predictable and stable - * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) - * - Order-independent cache keys work better with consistent build patterns - */ - static convolveMany(pmfList, eps = EPS) { - if (pmfList.length === 0) return _PMF.empty(eps); - if (pmfList.length === 1) return pmfList[0]; - return _PMF.reduceConvolveLeft(pmfList, eps); - } - /** - * Returns a plain, JSON-serializable representation of this PMF. - * - * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces - * the expected output (no double-encoding). Use {@link PMF.fromJSON} to - * reconstruct, or {@link PMF.toJSONString} if you need the string directly. - */ - toJSON() { - return { - bins: [...this.map.entries()], - normalized: this.normalized, - identifier: this.identifier - }; - } - /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ - toJSONString() { - return JSON.stringify(this); - } - static fromJSON(jsonData) { - return new _PMF( - new Map(jsonData.bins), - EPS, - !!jsonData.normalized, - jsonData.identifier || "fromJSON" - ); - } - /** - * Relative pruning with optional top-K floor. - * Keeps bins with p >= epsRel * peak, always keeps min and max damage, - * optionally guarantees at least `minBins` survivors by adding top-K. - * Returns a new, non-normalized PMF. - */ - prune(epsRel, minBins = 0) { - const size = this.map.size; - if (size === 0) return this; - let peak = 0; - let minDamage = Number.POSITIVE_INFINITY; - let maxDamage = Number.NEGATIVE_INFINITY; - for (const [dmg, bin] of this.map) { - if (bin.p > peak) peak = bin.p; - if (dmg < minDamage) minDamage = dmg; - if (dmg > maxDamage) maxDamage = dmg; - } - if (peak === 0) - return new _PMF(new Map(this.map), epsRel, false, this.identifier); - const thresh = epsRel * peak; - const entries = [...this.map.entries()]; - const survivorsByDmg = /* @__PURE__ */ new Map(); - const protect = (d) => { - const b = this.map.get(d); - if (b) survivorsByDmg.set(d, b); - }; - protect(minDamage); - if (maxDamage !== minDamage) protect(maxDamage); - for (const [dmg, bin] of entries) { - if (bin.p >= thresh) survivorsByDmg.set(dmg, bin); - } - if (minBins > 0 && survivorsByDmg.size < minBins) { - entries.sort((a, b) => b[1].p - a[1].p); - for (const [dmg, bin] of entries) { - if (!survivorsByDmg.has(dmg)) { - survivorsByDmg.set(dmg, bin); - if (survivorsByDmg.size >= minBins) break; - } - } - } - const prunedMap = /* @__PURE__ */ new Map(); - for (const [dmg, bin] of survivorsByDmg) { - const newCount = {}; - for (const k in bin.count) { - const v = bin.count[k]; - if (Math.abs(v) >= thresh) newCount[k] = v; - } - let newAttr; - if (bin.attr) { - for (const k in bin.attr) { - const v = bin.attr[k]; - if (Math.abs(v) >= thresh) { - if (!newAttr) newAttr = {}; - newAttr[k] = v; - } - } - } - prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr }); - } - return new _PMF(prunedMap, epsRel, false, `prune(${this.identifier})`); - } - /** Probability mass at exactly x. */ - pAt(x) { - return this.map.get(x)?.p ?? 0; - } - /** - * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. - * Assumes a miss is encoded as the damage-0 bin (the convention used across - * attack/save PMFs). The dual of {@link missProbability}. - */ - hitProbability() { - return 1 - this.pAt(0); - } - /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ - missProbability() { - return this.pAt(0); - } - /** - * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width - * damage buckets, aggregating probability mass (and `count`/`attr` - * provenance) into each bucket's start value. Returns this PMF unchanged when - * its integer support already fits within `maxBuckets`. - * - * This is a lossy display/downsampling transform (bucket start replaces the - * exact damage value) — use it for charting wide distributions, not for DPR - * math. - */ - rebin(maxBuckets) { - if (!(maxBuckets > 0)) return this; - const support = this.support(); - if (support.length === 0) return this; - const min = support[0]; - const max = support[support.length - 1]; - const range = max - min; - if (range + 1 <= maxBuckets) return this; - const binSize = Math.ceil((range + 1) / maxBuckets); - return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize); - } - /** Dense integer support from min..max (inclusive). - * Useful for showing empty bars in charts. - */ - denseSupport() { - const s = this.support(); - if (s.length === 0) return []; - const lo = Math.min(...s), hi = Math.max(...s); - return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort( - (a, b) => a - b - ); - } - /** CDF at x: P(X ≤ x). */ - cdfAt(x) { - let acc = 0; - for (const [val, bin] of this.map) if (val <= x) acc += bin.p; - return acc; - } - /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ - quantile(p) { - if (this.map.size === 0) return 0; - const s = this.support().sort((a, b) => a - b); - let acc = 0; - for (const x of s) { - acc += this.pAt(x); - if (acc >= p) return x; - } - return s[s.length - 1]; - } - /** Get outcome probability at specific damage value. */ - outcomeAt(damage, outcome) { - return this.map.get(damage)?.count[outcome] ?? 0; - } - /** Get all outcome types present in this PMF. */ - outcomes() { - const outcomeSet = /* @__PURE__ */ new Set(); - for (const [, bin] of this.map) { - for (const outcome in bin.count) { - if (bin.count[outcome] > 0) { - outcomeSet.add(outcome); - } - } - } - return Array.from(outcomeSet).sort(); - } - /** Get total probability of an outcome across all damage values. */ - outcomeProbability(outcome) { - let total = 0; - for (const [, bin] of this.map) { - total += bin.count[outcome] ?? 0; - } - return total; - } - /** Get damage attribution for an outcome at specific damage value. */ - outcomeAttributionAt(damage, outcome) { - return this.map.get(damage)?.attr?.[outcome] ?? 0; - } - /** Get all outcome data at specific damage value. */ - binAt(damage) { - const bin = this.map.get(damage); - if (!bin) return null; - return { - p: bin.p, - count: { ...bin.count }, - attr: bin.attr ? { ...bin.attr } : void 0 - }; - } - /** Check if outcome exists in this PMF. */ - hasOutcome(outcome) { - for (const [, bin] of this.map) { - if ((bin.count[outcome] ?? 0) > 0) { - return true; - } - } - return false; - } - /** - * Split each damage value's probability mass across outcome labels, returning - * per-label maps of `damage value → probability mass attributable to that - * label`. Summing over labels at a given value recovers that value's `p`. - * - * Damage-bearing bins are split by `attr` weight (the share of damage each - * outcome contributed); the clean-miss bin at 0 is split by `count` weight - * (there is no damage to attribute). Attribution is computed on demand via - * {@link withAttribution} when absent, so builder-generated PMFs work too. - * - * This is the provenance core of the stacked damage-attribution chart — the - * caller only maps these series into its rendering format (colors, binning, - * axis labels). - */ - attributionByValue() { - const src = this.hasAttribution() ? this : this.withAttribution(); - const result = /* @__PURE__ */ new Map(); - const add = (label, damage, mass) => { - if (!(mass > 0)) return; - let series = result.get(label); - if (!series) { - series = /* @__PURE__ */ new Map(); - result.set(label, series); - } - series.set(damage, (series.get(damage) ?? 0) + mass); - }; - for (const [damage, bin] of src.map) { - const p = bin.p || 0; - if (p <= 0) continue; - const isMissBin = damage === 0; - if (isMissBin) { - let totalCount = 0; - for (const k in bin.count) totalCount += bin.count[k] || 0; - if (totalCount > 0) { - const c = bin.count[MISS_NONE_OUTCOME] || 0; - add(MISS_NONE_OUTCOME, damage, c / totalCount * p); - } - continue; - } - let totalAttr = 0; - if (bin.attr) for (const k in bin.attr) totalAttr += bin.attr[k] || 0; - if (bin.attr && totalAttr > 0) { - for (const k in bin.attr) { - if (k === MISS_NONE_OUTCOME) continue; - add(k, damage, (bin.attr[k] || 0) / totalAttr * p); - } - } - } - return result; - } - tailProbGE(t) { - let s = 0; - for (const [x, bin] of this) { - if (bin.p > 0 && x >= t) s += bin.p; - } - return s; - } - tailProbGT(t) { - let s = 0; - for (const [x, rec] of this) { - if (x > t) s += rec.p; - } - return s; - } - /** - * Returns a new PMF containing only bins where the specified outcome has non-zero probability. - * This creates a marginal distribution for the given outcome type, with probabilities - * scaled to represent the unconditional mass attributable to that outcome. - */ - filterOutcome(outcome) { - const filteredMap = /* @__PURE__ */ new Map(); - for (const [damageValue, bin] of this.map) { - const outcomeCount = bin.count[outcome] ?? 0; - const totalCount = Object.values(bin.count ?? {}).reduce( - (a, b) => (a ?? 0) + (b ?? 0), - 0 - ); - if (outcomeCount > 0 && totalCount !== void 0 && totalCount > 0) { - const proportion = outcomeCount / totalCount; - const newP = bin.p * proportion; - const newCount = { [outcome]: outcomeCount }; - let newAttr; - if (bin.attr && bin.attr[outcome] !== void 0) { - newAttr = { [outcome]: bin.attr[outcome] * proportion }; - } - filteredMap.set(damageValue, { - p: newP, - count: newCount, - attr: newAttr - }); - } - } - return new _PMF( - filteredMap, - this.epsilon, - false, - // don't normalize by default - `filter(${this.identifier},${outcome})` - ); - } - /** - * Calculates probabilities for first-success outcomes across n independent attempts. - * - * @param pSuccess - Total probability of any success on a single attempt. - * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). - * @param n - Number of independent attempts. - * - * Returns: - * - pSpecificSuccess: Probability that the first success was of the "special" type - * - pGeneralSuccess: Probability that the first success was of the non-special type - * - pNone: Probability that no successes occurred - * - pAny: Probability that at least one success occurred - */ - static firstSuccessWeights(pSuccess, pSpecial, n) { - if (!Number.isFinite(pSuccess) || !Number.isFinite(pSpecial) || pSuccess < 0 || pSuccess > 1 || pSpecial < 0 || pSpecial - pSuccess > EPS) { - throw new Error( - `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})` - ); - } - const pFail = 1 - pSuccess; - const pFailAll = Math.pow(pFail, n); - const pAny = 1 - pFailAll; - const denom = pSuccess === 0 ? 1 : pSuccess; - const pSpecificSuccess = pSpecial * pAny / denom; - const pGeneralSuccess = (pSuccess - pSpecial) * pAny / denom; - const pNone = 1 - pSpecificSuccess - pGeneralSuccess; - return { pSpecificSuccess, pGeneralSuccess, pNone, pAny }; - } - mapValues(f, eps = EPS, opts) { - const rounding = opts?.rounding ?? "none"; - const preserveCounts = opts?.preserveCounts ?? true; - const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x; - const probs = /* @__PURE__ */ new Map(); - const counts = /* @__PURE__ */ new Map(); - for (const [v, bin] of this) { - if (Math.abs(bin.p) < eps) continue; - const u = round(f(v)); - probs.set(u, (probs.get(u) ?? 0) + bin.p); - if (preserveCounts) { - const src = bin.count; - if (src) { - const dest = counts.get(u) ?? {}; - for (const k in src) { - dest[k] = (dest[k] ?? 0) + src[k]; - } - counts.set(u, dest); - } - } - } - const internal = /* @__PURE__ */ new Map(); - for (const [u, p] of probs) { - internal.set(u, { p, count: counts.get(u) ?? {} }); - } - return _PMF.fromMap( - new Map(Array.from(internal, ([u, b]) => [u, b.p])), - eps - ); - } - static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) { - const filtered = []; - for (const [v, p] of m) { - if (!Number.isFinite(v) || !Number.isFinite(p)) continue; - if (p <= 0 || Math.abs(p) < eps) continue; - if (requireIntegerValues && !Number.isInteger(v)) { - throw new Error(`fromMap: non-integer outcome ${v}`); - } - filtered.push([v, p]); - } - if (filtered.length === 0) { - throw new Error("fromMap: empty or invalid input map"); - } - let sum = 0; - let c = 0; - for (const [, p] of filtered) { - const y = p - c; - const t = sum + y; - c = t - sum - y; - sum = t; - } - if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0"); - filtered.sort((a, b) => a[0] - b[0]); - const internal = /* @__PURE__ */ new Map(); - for (const [v, p] of filtered) { - internal.set(v, { p: p / sum, count: {} }); - } - return new _PMF(internal, eps); - } - query() { - return new DiceQuery(this); - } -}; -// Unique ID generator for anonymous PMFs to avoid cache key collisions -_PMF.__anonIdCounter = 1; -var PMF = _PMF; - -// src/parser/dice.ts -var MAX_BINARY_OUTCOMES = 1e8; -var Dice = class _Dice { - constructor(x = 0) { - this.faces = {}; - this.privateData = {}; - // Partial: the object starts empty and gains keys as outcomes are recorded, - // so the type must not claim every OutcomeType is present. (Previously typed - // as a full Record via an `as` cast, which lied about missing keys.) - this.outcomeData = {}; - this.hasHitDistributionCalculated = false; - if (x <= 0) return; - for (let i = 1; i <= x; i++) { - this.faces[i] = 1; - } - } - getOutcomeDistribution(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const distribution = this.outcomeData[key]; - if (distribution === void 0) return void 0; - return { ...distribution }; - } - getFullOutcomeDistribution() { - return { ...this.outcomeData }; - } - setOutcomeDistribution(key, data) { - if (data) { - this.outcomeData[key] = data; - } else { - delete this.outcomeData[key]; - } - } - hasOutcomeData(key) { - if (key === "hit") { - this.ensureHitDistribution(); - } - const data = this.outcomeData[key]; - return data !== void 0 && Object.keys(data).length > 0; - } - getOutcomeCount(key, face) { - return this.outcomeData[key]?.[face] ?? 0; - } - getAverage(key) { - const distribution = this.getOutcomeDistribution(key); - if (!distribution) return 0; - const totalCount = Object.values(distribution).reduce( - (sum, count) => sum + count, - 0 - ); - const expectedDamage = Object.entries(distribution).reduce( - (sum, [damage, count]) => sum + Number(damage) * count, - 0 - ); - if (totalCount === 0) return 0; - return expectedDamage / totalCount; - } - // TODO this can be private later if we change how testing works - calculateHitDistribution() { - const hitValues = {}; - const subtractedOutcomes = [ - this.outcomeData.crit, - this.outcomeData.missNone, - this.outcomeData.missDamage, - this.outcomeData.saveHalf, - this.outcomeData.saveFail, - this.outcomeData.pc - ]; - for (const [face, totalCount] of Object.entries(this.faces)) { - const numFace = Number(face); - let hitCount = totalCount; - for (const distribution of subtractedOutcomes) { - const outcomeCount = distribution?.[numFace]; - if (outcomeCount) { - hitCount -= outcomeCount; - } - } - if (numFace === 0) { - hitCount = 0; - } - if (hitCount < 0) { - hitCount = 0; - } - hitValues[numFace] = hitCount; - } - return hitValues; - } - ensureHitDistribution() { - if (!this.hasHitDistributionCalculated) { - const hitValues = this.calculateHitDistribution(); - this.setOutcomeDistribution("hit", hitValues); - this.hasHitDistributionCalculated = true; - } - } - // PRIVATE FUNCTIONS - binaryOp(other, op, diceConstructor) { - const result = diceConstructor ? diceConstructor() : new _Dice(); - const isScalar = typeof other === "number"; - const keys1 = this.keys(); - const keys2 = isScalar ? [] : other.keys(); - if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) { - throw new DiceParseError( - `Dice operation over ${keys1.length}\xD7${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}` - ); - } - for (const key1 of keys1) { - const value1 = this.faces[key1]; - if (isScalar) { - const resultKey = op(key1, other); - result.increment(resultKey, value1); - } else { - for (const key2 of keys2) { - const value2 = other.faces[key2]; - const resultKey = op(key1, key2); - result.increment(resultKey, value1 * value2); - } - } - } - return result; - } - removeFaces(facesToRemove) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (!facesToRemove.includes(numKey)) { - result.faces[numKey] = value; - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // PUBLIC FUNCTIONS - getFaceEntries() { - return Object.entries(this.faces).map(([k, v]) => [Number(k), v]); - } - getFaceMap() { - return { ...this.faces }; - } - get(face) { - return this.faces[face] ?? 0; - } - keys() { - return Object.keys(this.faces).map(Number); - } - values() { - return Object.values(this.faces); - } - total() { - return Object.values(this.faces).reduce((sum, value) => sum + value, 0); - } - setFace(key, value) { - this.faces[key] = value; - } - static scalar(value) { - const result = new _Dice(); - result.increment(value, 1); - return result; - } - maxFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.max(...numericKeys); - } - minFace() { - const numericKeys = this.keys(); - if (numericKeys.length === 0) { - throw new Error("No numeric faces found"); - } - return Math.min(...numericKeys); - } - increment(face, count) { - const current = this.faces[face] || 0; - this.faces[face] = current + count; - } - normalize(scalar) { - const result = new _Dice(); - for (const [face, count] of Object.entries(this.faces)) { - result.faces[Number(face)] = count * scalar; - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - // OPERATIONS - add(other) { - return this.binaryOp(other, (a, b) => a + b); - } - subtract(other) { - return this.binaryOp(other, (a, b) => a - b); - } - conditionalApply(other) { - return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b); - } - multiply(other) { - return this.binaryOp(other, (a, b) => a * b); - } - addNonZero(other) { - return this.binaryOp(other, (a, b) => a !== 0 ? a + b : a); - } - eq(other) { - return this.binaryOp(other, (a, b) => a === b ? 1 : 0); - } - max(other) { - return this.binaryOp(other, (a, b) => Math.max(a, b)); - } - min(other) { - return this.binaryOp(other, (a, b) => Math.min(a, b)); - } - advantage() { - return this.max(this); - } - ge(other) { - return this.binaryOp(other, (a, b) => a >= b ? 0 : 1); - } - divide(other) { - return this.binaryOp(other, (a, b) => a / b); - } - divideRoundUp(other) { - return this.binaryOp(other, (a, b) => Math.ceil(a / b)); - } - divideRoundDown(other) { - return this.binaryOp(other, (a, b) => Math.floor(a / b)); - } - and(other) { - return this.binaryOp(other, (a, b) => a && b ? 1 : 0); - } - checkTarget(other, comparisonLogic) { - const createResult = () => { - const result = new _Dice(); - result.increment(0, 0); - result.increment(1, 0); - return result; - }; - return this.binaryOp(other, comparisonLogic, createResult); - } - dc(other) { - const dcCheck = (a, b) => a >= b ? 0 : 1; - const result = this.checkTarget(other, dcCheck); - result.privateData.isDCCheck = true; - return result; - } - ac(other) { - const acCheck = (a, b) => a >= b ? a : 0; - return this.checkTarget(other, acCheck); - } - deleteFace(face) { - const result = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - if (numKey !== face) { - result.increment(numKey, value); - } - } - result.privateData = { ...this.privateData }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - reroll(toReroll) { - const rerollDice = typeof toReroll === "number" ? _Dice.scalar(toReroll) : toReroll; - const rerollKeys = rerollDice.keys(); - const rerollSet = new Set(rerollKeys); - const removed = this.removeFaces(rerollKeys); - let result = new _Dice(); - for (const face of this.keys()) { - const wasRerolled = rerollSet.has(face); - result = result.combine(removed); - if (wasRerolled) { - result = result.combine(this); - } - } - return result; - } - // This is not addition and not rolling two dice at once. - // Instead, it’s mixing two distributions into a single weighted die. - combine(other) { - if (typeof other === "number") { - other = _Dice.scalar(other); - } - const result = new _Dice(); - for (const [key, value] of Object.entries(other.faces)) { - result.faces[Number(key)] = value; - } - const except = new _Dice(); - for (const [key, value] of Object.entries(this.faces)) { - const numKey = Number(key); - result.increment(numKey, value); - if (!(numKey in other.faces)) { - except.increment(numKey, value); - } - } - result.privateData = { ...this.privateData, except: other }; - result.outcomeData = { ...this.outcomeData }; - return result; - } - combineInPlace(other) { - for (const [key, value] of Object.entries(other.faces)) { - const numKey = Number(key); - const current = this.faces[numKey] || 0; - this.faces[numKey] = current + value; - } - } - percent() { - const total = this.total(); - const result = {}; - for (const [face, count] of Object.entries(this.faces)) { - result[Number(face)] = count / total; - } - return result; - } - average() { - const total = this.total(); - if (total === 0) return 0; - let sum = 0; - for (const [key, value] of Object.entries(this.faces)) { - sum += Number(key) * value; - } - return sum / total; - } - /* - * Convert dice to PMF using OutcomeType labels directly from damage distribution. - * This is much cleaner than the original complex distribution conversion. - */ - toPMF(numEpsilon = EPS) { - const total = this.total(); - if (total === 0) return PMF.empty(numEpsilon); - this.ensureHitDistribution(); - const map = /* @__PURE__ */ new Map(); - const hitDistro = this.getOutcomeDistribution("hit") || {}; - const critDistro = this.getOutcomeDistribution("crit") || {}; - const missDistro = this.getOutcomeDistribution("missDamage") || {}; - const saveDistro = this.getOutcomeDistribution("saveHalf") || {}; - const pcDistro = this.getOutcomeDistribution("pc") || {}; - const isSaveHalf = Object.keys(saveDistro).length > 0; - const isDCCheck = this.privateData.isDCCheck === true; - const clampNonNeg = (x) => x < 0 && x > -1e-15 ? 0 : x; - for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) { - const face = Number(faceStr); - const faceCount = Number(faceCountRaw); - if (faceCount <= 0) continue; - let p = faceCount / total; - p = clampNonNeg(p); - if (!(p > 0)) continue; - if (numEpsilon >= 0 && p < numEpsilon) continue; - const count = {}; - const attr = {}; - if (hitDistro[face]) { - const c = clampNonNeg(hitDistro[face] / total); - if (c > 0) { - if (isSaveHalf || isDCCheck) { - count.saveFail = c; - attr.saveFail = clampNonNeg(face * hitDistro[face] / total); - } else { - count.hit = c; - attr.hit = clampNonNeg(face * hitDistro[face] / total); - } - } - } - if (critDistro[face]) { - const c = clampNonNeg(critDistro[face] / total); - if (c > 0) { - count.crit = c; - attr.crit = clampNonNeg(face * critDistro[face] / total); - } - } - if (missDistro[face]) { - const c = clampNonNeg(missDistro[face] / total); - if (c > 0) { - count.missDamage = c; - attr.missDamage = clampNonNeg(face * missDistro[face] / total); - } - } - if (saveDistro[face]) { - const c = clampNonNeg(saveDistro[face] / total); - if (c > 0) { - if (isSaveHalf) { - count.saveHalf = c; - attr.saveHalf = clampNonNeg(face * saveDistro[face] / total); - } else { - count.saveFail = (count.saveFail ?? 0) + c; - attr.saveFail = clampNonNeg( - (attr.saveFail ?? 0) + face * saveDistro[face] / total - ); - } - } - } - if (pcDistro[face]) { - const c = clampNonNeg(pcDistro[face] / total); - if (c > 0) { - count.pc = c; - attr.pc = clampNonNeg(face * pcDistro[face] / total); - } - } - if (!isSaveHalf && !isDCCheck) { - const distroCountRaw = (hitDistro[face] || 0) + (critDistro[face] || 0) + (missDistro[face] || 0) + (saveDistro[face] || 0) + (pcDistro[face] || 0); - const unaccountedCount = clampNonNeg(faceCount - distroCountRaw); - if (unaccountedCount > 0) { - const frac = clampNonNeg(unaccountedCount / total); - if (frac > 0) { - count.missNone = (count.missNone ?? 0) + frac; - } - } - } - const bin = { p, count }; - if (Object.keys(attr).length > 0) { - bin.attr = attr; - } - map.set(face, bin); - } - const identifier = this.identifier || "ERROR"; - return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true); - } -}; - -// src/parser/parser.ts -var MAX_DIE_SIDES = 1e6; -var MAX_DICE_COUNT = 1e4; -var MAX_KEEP_OUTCOMES = 1e6; -var parseCache = new LRUCache(1e3); -var cachingEnabled = true; -function setCachingEnabled(enabled) { - cachingEnabled = enabled; - if (!enabled) clearParserCache(); -} -function getCachingEnabled() { - return cachingEnabled; -} -function clearParserCache() { - parseCache.clear(); -} -function parse(expression, n = 0) { - const cleaned = expression.replace(/ /g, "").toLowerCase(); - if (cachingEnabled) { - const cacheKey = `${cleaned}:${n}`; - const cached = parseCache.get(cacheKey); - if (cached) return cached; - } - const chars = [...cleaned]; - let result; - try { - result = parseExpression(chars, n); - } catch (error) { - throw new DiceParseError( - `Cannot parse dice expression [${expression}]: ${error}`, - { expression, cause: error } - ); - } - result.privateData = result.privateData || {}; - result.identifier = cleaned; - if (chars.length > 0) { - throw new DiceParseError( - `Unexpected token: '${chars[0]}' from expression: '${expression}'`, - { expression } - ); - } - const resultPMF = result.toPMF(-1); - if (cachingEnabled) { - const cacheKey = `${cleaned}:${n}`; - parseCache.set(cacheKey, resultPMF); - } - return resultPMF; -} -function combineDiceWithNormalization(dice, normValue, outcomeType, currentNorm, finalResult) { - dice = dice.normalize(currentNorm); - finalResult = finalResult.normalize(normValue); - finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap()); - finalResult = finalResult.combine(dice); - return { newNorm: currentNorm * normValue, updatedResult: finalResult }; -} -function parseExpression(arr, n) { - const result = (() => { - const res = parseArgument(arr, n); - return typeof res === "number" ? Dice.scalar(res) : res; - })(); - let op = parseOperation(arr); - let finalResult = result; - while (op != null) { - const arg = !op.unary ? parseArgument(arr, n) : finalResult; - let crit; - let critNorm = 1; - if (arr[0] === "x" || arr[0] === "c") { - const isXcrit = arr[0] === "x"; - if (isXcrit) assertToken(arr, "x"); - assertToken(arr, "c"); - assertToken(arr, "r"); - assertToken(arr, "i"); - assertToken(arr, "t"); - const count = isXcrit ? parseNumber(arr, n) : 1; - crit = new Dice(); - for (let i = 0; i < count; i++) { - const max = finalResult.maxFace(); - crit.setFace(max, finalResult.get(max)); - finalResult = finalResult.deleteFace(max); - } - critNorm = crit.total(); - crit = op.call(crit, parseBinaryArgument(arg, arr, n)); - critNorm = crit && critNorm ? crit.total() / critNorm : 1; - } - let save; - let saveNorm = 1; - if (arr[0] === "s") { - assertToken(arr, "s"); - assertToken(arr, "a"); - assertToken(arr, "v"); - assertToken(arr, "e"); - save = new Dice(); - const min = finalResult.minFace(); - save.increment(min > 0 ? min : 1, finalResult.get(min)); - saveNorm = save.total(); - finalResult = finalResult.deleteFace(min); - save = op.call(save, parseBinaryArgument(arg, arr, n)); - saveNorm = save && saveNorm ? save.total() / saveNorm : 1; - } - let pc; - let pcNorm = 1; - if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") { - assertToken(arr, "p"); - assertToken(arr, "c"); - pc = new Dice(); - const min = finalResult.minFace(); - pc.increment(min > 0 ? min : 1, finalResult.get(min)); - const missBefore = pc.total(); - finalResult = finalResult.deleteFace(min); - pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); - const missAfter = pc ? pc.total() : 0; - pcNorm = missBefore ? missAfter / missBefore : 1; - } - let miss; - let missNorm = 1; - if (arr[0] === "m") { - assertToken(arr, "m"); - assertToken(arr, "i"); - assertToken(arr, "s"); - assertToken(arr, "s"); - miss = new Dice(); - const min = finalResult.minFace(); - miss.increment(min > 0 ? min : 1, finalResult.get(min)); - missNorm = miss.total(); - finalResult = finalResult.deleteFace(min); - miss = op.call(miss, parseBinaryArgument(arg, arr, n)); - missNorm = miss && missNorm ? miss.total() / missNorm : 1; - } - let norm = finalResult.total(); - finalResult = op.call(finalResult, arg); - norm = norm ? finalResult.total() / norm : 1; - if (crit) { - const result2 = combineDiceWithNormalization( - crit, - critNorm, - "crit", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (save) { - const result2 = combineDiceWithNormalization( - save, - saveNorm, - "saveHalf", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (miss) { - const result2 = combineDiceWithNormalization( - miss, - missNorm, - "missDamage", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - if (pc) { - const result2 = combineDiceWithNormalization( - pc, - pcNorm, - "pc", - norm, - finalResult - ); - norm = result2.newNorm; - finalResult = result2.updatedResult; - } - op = parseOperation(arr); - } - return finalResult; -} -function parseArgument(s, n) { - let result = parseArgumentInternal(s, n); - while (true) { - const next = parseArgumentInternal(s, n); - if (next === void 0) break; - result = multiplyDiceByDice(result, next); - } - return result; -} -function multiplyDiceByDice(d1, d2) { - if (typeof d1 === "number") d1 = Dice.scalar(d1); - if (typeof d2 === "number") d2 = Dice.scalar(d2); - const result = new Dice(); - const faces = /* @__PURE__ */ new Map(); - let normalizationFactor = 1; - for (const key of d1.keys()) { - let face; - if (typeof key !== "number") { - continue; - } - if (d2.privateData.keep) { - const faceCount = d2.keys().length; - if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) { - throw new DiceParseError( - `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}` - ); - } - const repeat = Array(key).fill(d2); - face = opDice(repeat, d2.privateData.keep); - } else { - face = multiplyDice(key, d2); - } - normalizationFactor *= face.total(); - faces.set(key, face); - } - for (const [k, face] of faces) { - const count = d1.get(k); - result.combineInPlace( - face.normalize(count * normalizationFactor / face.total()) - ); - } - result.privateData.except = {}; - return result; -} -function multiplyDice(n, d) { - if (n > MAX_DICE_COUNT) { - throw new DiceParseError( - `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}` - ); - } - if (n === 0) return new Dice(0); - if (n === 1) return d; - const half = Math.floor(n / 2); - let result = multiplyDice(half, d); - result = result.add(result); - if (n % 2 === 1) { - result = result.add(d); - } - return result; -} -function opDice(diceList, keepFn) { - return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn); -} -function opDiceInternal(diceList, result, index, values, weight, combineFn) { - if (index === diceList.length) { - return result.combine(Dice.scalar(combineFn(values)).normalize(weight)); - } - const currentDice = diceList[index]; - for (const face of currentDice.keys()) { - values.push(face); - result = opDiceInternal( - diceList, - result, - index + 1, - values, - weight * currentDice.get(face), - combineFn - ); - values.pop(); - } - return result; -} -function parseArgumentInternal(s, n) { - if (s.length === 0) return; - const c = s[0]; - switch (c) { - case "(": - s.shift(); - return assertToken(s, ")", parseExpression(s, n)); - case "h": - case "d": - return parseDice(s, n); - case "k": - assertToken(s, "k"); - return parseKeep(s, n); - case "n": - return parseNumber(s, n); - default: - if (isDigit(c)) return parseNumber(s, n); - return; - } -} -function parseBinaryArgument(arg, arr, n) { - if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) { - assertToken(arr, "half"); - const diceArg = typeof arg === "number" ? Dice.scalar(arg) : arg; - return diceArg.divideRoundDown(2); - } - const parsed = parseArgument(arr, n); - return typeof parsed === "number" ? Dice.scalar(parsed) : parsed; -} -function assertToken(s, expected, ret) { - for (const ch of expected) { - const found = s.shift(); - if (found !== ch) { - throw new Error(`Expected character '${ch}', found '${found}'`); - } - } - return ret; -} -function parseDice(s, n) { - let rerollOne = false; - if (peek(s, "hd") && peekIsNumber(s, 2)) { - assertToken(s, "h"); - assertToken(s, "d"); - rerollOne = true; - } else if (peek(s, "d") && peekIsNumber(s, 1)) { - assertToken(s, "d"); - } else { - return; - } - const sides = parseNumber(s, n); - if (sides > MAX_DIE_SIDES) { - throw new DiceParseError( - `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}` - ); - } - let result = new Dice(sides); - if (rerollOne) { - result = result.reroll(1); - } - return result; -} -function peek(arr, expected) { - if (expected.length > arr.length) return false; - for (let i = 0; i < expected.length; i++) { - if (arr[i] !== expected.charAt(i)) return false; - } - return true; -} -function peekIsNumber(arr, index) { - if (index >= arr.length) return false; - return isDigit(arr[index]) || arr[index] === "n"; -} -function parseNumber(s, n) { - let ret = ""; - while (s.length > 0 && (isDigit(s[0]) || s[0] === "n")) { - const ch = s.shift(); - ret += ch === "n" ? n.toString() : ch; - } - if (ret.length === 0) { - throw new Error(`Expected number, found: '${s[0]}'`); - } - return parseInt(ret, 10); -} -function isDigit(c) { - return c >= "0" && c <= "9"; -} -function parseKeep(s, n) { - let keepLowest = false; - if (peek(s, "l")) { - assertToken(s, "l"); - keepLowest = true; - } else if (peek(s, "h")) { - assertToken(s, "h"); - keepLowest = false; - } else { - return; - } - const keepCount = parseNumber(s, n); - const result = parseArgumentInternal(s, n); - if (result instanceof Dice) { - result.privateData.keep = keepN(keepCount, keepLowest); - return result; - } - throw new Error("Expected Dice after keep modifier"); -} -function keepN(n, low) { - return (values) => { - const sorted = [...values].sort((a, b) => low ? a - b : b - a); - return sorted.slice(0, n).reduce((sum, val) => sum + val, 0); - }; -} -function parseOperation(s) { - switch (s[0]) { - case ")": - return; - case "a": - assertToken(s, "ac"); - return Dice.prototype.ac; - case "d": - assertToken(s, "dc"); - return Dice.prototype.dc; - case "!": - assertToken(s, "!"); - const adv = Dice.prototype.advantage; - adv.unary = true; - return adv; - case ">": - assertToken(s, ">"); - return Dice.prototype.max; - case "<": - assertToken(s, "<"); - return Dice.prototype.min; - case "+": - assertToken(s, "+"); - return Dice.prototype.addNonZero; - case "~": - assertToken(s, "~"); - assertToken(s, "+"); - return Dice.prototype.add; - case "-": - assertToken(s, "-"); - return Dice.prototype.subtract; - case "&": - assertToken(s, "&"); - return Dice.prototype.combine; - case "r": - assertToken(s, "reroll"); - return Dice.prototype.reroll; - case "*": - assertToken(s, "*"); - if (peek(s, "*")) { - assertToken(s, "*"); - return Dice.prototype.multiply; - } - return Dice.prototype.conditionalApply; - case "/": - assertToken(s, "/"); - if (s[0] === "/") { - assertToken(s, "/"); - return Dice.prototype.divideRoundDown; - } - return Dice.prototype.divideRoundUp; - case "=": - assertToken(s, "="); - return Dice.prototype.eq; - } - return; -} - -// src/pmf/mixture.ts -var Mixture = class _Mixture { - constructor(eps = EPS) { - this.totals = /* @__PURE__ */ new Map(); - // raw mass per outcome (pre-normalization) - this.labelMass = /* @__PURE__ */ new Map(); - this.eps = Number.isFinite(eps) ? eps : EPS; - } - /** Remove all accumulated state. */ - clear() { - this.totals.clear(); - this.labelMass.clear(); - return this; - } - /** Number of distinct outcome values currently accumulated. */ - size() { - return this.totals.size; - } - /** Whether a label was ever added. */ - hasLabel(label) { - for (const bag of this.labelMass.values()) if (bag[label]) return true; - return false; - } - /** - * Add a labeled component with a mixture weight. - * Weight can be any positive finite number. Very small contributions are pruned by eps. - */ - add(label, pmf, weight = 1) { - if (!Number.isFinite(weight) || weight <= 0) return this; - for (const [v, bin] of pmf) { - const p = bin.p; - if (p <= 0) continue; - const add = weight * p; - if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue; - this.totals.set(v, (this.totals.get(v) ?? 0) + add); - const bag = this.labelMass.get(v) ?? {}; - bag[label] = (bag[label] ?? 0) + add; - this.labelMass.set(v, bag); - } - return this; - } - buildPMF(eps = EPS) { - let grand = 0; - let c = 0; - for (const m of this.totals.values()) { - const y = m - c; - const t = grand + y; - c = t - grand - y; - grand = t; - } - if (!(grand > 0)) throw new Error("Mixture: zero total mass"); - const internal = /* @__PURE__ */ new Map(); - for (const [v, m] of this.totals) { - if (m <= 0 || Math.abs(m) < this.eps) continue; - const count = this.labelMass.get(v) ?? {}; - internal.set(v, { p: m / grand, count }); - } - return new PMF(internal, eps); - } - /** - * Produce normalized *per-label* PMFs (labels independent). - * These are unlabeled PMFs built from the raw mass of that label alone. - */ - byOutcome() { - const labels = /* @__PURE__ */ new Set(); - for (const bag of this.labelMass.values()) { - for (const k of Object.keys(bag)) labels.add(k); - } - const out = {}; - for (const label of labels) { - const m = /* @__PURE__ */ new Map(); - for (const [v, bag] of this.labelMass) { - const w = bag[label]; - if (w && Math.abs(w) >= this.eps) m.set(v, w); - } - if (m.size > 0) out[label] = PMF.fromMap(m, this.eps); - } - return out; - } - /** - * Mixture weights per label, normalized to sum to 1 over labels that appeared. - * Uses raw mass before per-outcome normalization. - */ - weights() { - const res = {}; - for (const [, bag] of this.labelMass) { - for (const [lab, w] of Object.entries(bag)) { - if (!Number.isFinite(w) || w <= 0) continue; - res[lab] = (res[lab] ?? 0) + w; - } - } - let total = 0; - let c = 0; - for (const v of Object.values(res)) { - const y = v - c; - const t = total + y; - c = t - total - y; - total = t; - } - if (total > 0) { - for (const k in res) res[k] = res[k] / total; - } - return res; - } - toJSON() { - return { - totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]), - labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]), - eps: this.eps - }; - } - static mix(items, eps = EPS) { - const mix = new _Mixture(eps); - for (const [lab, pmf, w] of items) mix.add(lab, pmf, w); - return mix.buildPMF(); - } -}; - -export { ALL_OUTCOME_TYPES, DiceParseError, DiceQuery, EPS, LRUCache, MISS_NONE_OUTCOME, Mixture, OUTCOME_DISPLAY_ORDER, PMF, calculateBounceOdds, clearParserCache, critProbability, getCachingEnabled, onAnyHit, onCritOnly, onHitOnly, onMissDamageOnly, onMissOnly, onPotentCantripOnly, onSaveFailOnly, onSaveHalfOnly, parse, pmfCache, setCachingEnabled, sortOutcomes }; -//# sourceMappingURL=index.js.map -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index 7a1d8bc..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/common/bounce.ts","../src/common/errors.ts","../src/common/lru-cache.ts","../src/common/types.ts","../src/pmf/query.ts","../src/pmf/pmf.ts","../src/parser/dice.ts","../src/parser/parser.ts","../src/pmf/mixture.ts"],"names":["cumulativeSum","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Bounce odds — the \"birthday problem\" for bouncing damage dice (e.g. Chromatic\n * Orb): the probability that at least two of K dice with S faces show the same\n * value, which is what lets the spell jump to another target.\n *\n * Accounts for two modifiers:\n * - **Elemental Adept** (`minimumDieRoll >= 2`): rolls below the minimum are\n * bumped up to it, collapsing the low faces onto a single heavier value.\n * - **Empowered Spell** (`rerollDamageDice > 0`): a number of dice may be\n * rerolled once, giving a second chance at a match.\n *\n * The base and Elemental-Adept cases are computed exactly (see\n * {@link pAllDistinct}); the Empowered-Spell reroll is an explicit model layered\n * on the exact base match probability.\n */\n\n/** Options that modify bounce odds via metamagic / feats. */\nexport interface BounceOddsOptions {\n /** Minimum die roll — e.g. 2 for Elemental Adept, 3 for Great Weapon Fighting 2024. */\n minimumDieRoll?: number;\n /** Number of dice that may be rerolled once — e.g. CHA modifier for Empowered Spell. */\n rerollDamageDice?: number;\n}\n\n/** Binomial coefficient C(n, k), 0 for out-of-range k. */\nfunction binom(n: number, k: number): number {\n if (k < 0 || k > n) return 0;\n let result = 1;\n for (let i = 0; i < k; i++) result = (result * (n - i)) / (i + 1);\n return result;\n}\n\n/**\n * Exact P(all K dice show distinct values) for a die with `uniformCount`\n * ordinary faces (each probability `1/faces`) plus one optional heavy face whose\n * probability is `heavyWeight` (used for the Elemental-Adept collapse; pass 0\n * for a plain die). Uses the elementary symmetric polynomial e_K over the face\n * probabilities: P(all distinct) = K! · e_K.\n */\nfunction pAllDistinct(\n dice: number,\n faces: number,\n uniformCount: number,\n heavyWeight: number\n): number {\n const light = 1 / faces;\n // e_K = (choose K distinct light faces) + (heavy face + K-1 light faces).\n const eK =\n binom(uniformCount, dice) * Math.pow(light, dice) +\n heavyWeight * binom(uniformCount, dice - 1) * Math.pow(light, dice - 1);\n let kFactorial = 1;\n for (let i = 2; i <= dice; i++) kFactorial *= i;\n return kFactorial * eK;\n}\n\n/** Exact P(at least one duplicate) among `dice` dice, honoring Elemental Adept. */\nfunction pMatch(dice: number, faces: number, minimumDieRoll: number): number {\n if (dice <= 1) return 0;\n if (dice > faces) return 1;\n\n if (minimumDieRoll >= 2) {\n // Rolls 1..minimumDieRoll collapse onto the value `minimumDieRoll`, giving it\n // weight minimumDieRoll/faces; the faces above it stay uniform at 1/faces.\n const uniformCount = faces - minimumDieRoll; // values minimumDieRoll+1 .. faces\n const effectiveValues = uniformCount + 1; // + the collapsed value\n if (dice > effectiveValues) return 1;\n const heavyWeight = minimumDieRoll / faces;\n const distinct = pAllDistinct(dice, faces, uniformCount, heavyWeight);\n return Math.min(1, Math.max(0, 1 - distinct));\n }\n\n // Plain die: P(all distinct) = falling_factorial(faces, dice) / faces^dice.\n let pDistinct = 1;\n for (let i = 0; i < dice; i++) pDistinct *= (faces - i) / faces;\n return 1 - pDistinct;\n}\n\n/**\n * P(at least two of `diceCount` dice with `dieFaces` faces match), honoring\n * Elemental Adept and Empowered Spell. Returns a probability in [0, 1].\n *\n * @param diceCount Number of dice rolled.\n * @param dieFaces Faces per die (e.g. 8 for d8).\n * @param options Optional metamagic / feat modifiers.\n */\nexport function calculateBounceOdds(\n diceCount: number,\n dieFaces: number,\n options?: BounceOddsOptions\n): number {\n if (diceCount <= 1) return 0;\n if (diceCount > dieFaces) return 1; // pigeonhole\n\n const minimumDieRoll = options?.minimumDieRoll ?? 0;\n const rerollDamageDice = options?.rerollDamageDice ?? 0;\n\n const pMatchFirst = pMatch(diceCount, dieFaces, minimumDieRoll);\n\n // Without Empowered Spell we're done.\n const rerollCount = Math.min(rerollDamageDice, diceCount);\n if (rerollCount <= 0) return pMatchFirst;\n\n // Empowered Spell: reroll `rerollCount` non-matching dice once. Model the\n // second chance as (a rerolled die matching one of the kept dice) OR (the\n // rerolled dice matching among themselves).\n const pNoMatchFirst = 1 - pMatchFirst;\n const keptDice = diceCount - rerollCount;\n const effectiveFaces = minimumDieRoll >= 2 ? dieFaces - (minimumDieRoll - 1) : dieFaces;\n\n // With no kept dice, a rerolled die vacuously \"misses\" all of them (prob 1), so\n // the only way to match is among the rerolled dice themselves (pRerolledMatch below).\n const pRerollDieMissesAll =\n keptDice > 0 ? Math.pow((effectiveFaces - keptDice) / effectiveFaces, rerollCount) : 1;\n const pAtLeastOneRerollMatches = 1 - pRerollDieMissesAll;\n const pRerolledMatch = rerollCount >= 2 ? pMatch(rerollCount, dieFaces, minimumDieRoll) : 0;\n const pMatchAfterReroll = Math.min(\n 1,\n pAtLeastOneRerollMatches + pRerolledMatch * (1 - pAtLeastOneRerollMatches)\n );\n\n return Math.min(1, pMatchFirst + pNoMatchFirst * pMatchAfterReroll);\n}\n","/**\n * Error thrown when a dice expression cannot be parsed.\n *\n * Extends the built-in {@link Error}, so existing `catch (e)` / message checks\n * continue to work, while callers can now narrow with `instanceof DiceParseError`.\n *\n * @example\n * try {\n * parse(\"d6@3\");\n * } catch (e) {\n * if (e instanceof DiceParseError) {\n * // e.expression === \"d6@3\"\n * }\n * }\n */\nexport class DiceParseError extends Error {\n /** The original expression that failed to parse, when available. */\n readonly expression?: string;\n\n /** The underlying error that triggered this one, when available. */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: { expression?: string; cause?: unknown }\n ) {\n super(message);\n this.name = \"DiceParseError\";\n this.expression = options?.expression;\n this.cause = options?.cause;\n // Restore the prototype chain for reliable `instanceof` across targets.\n Object.setPrototypeOf(this, DiceParseError.prototype);\n }\n}\n","/**\n * Simple LRU cache implementation\n */\n\nexport class LRUCache {\n private cache = new Map();\n\n constructor(private readonly maxSize = 1000) {}\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value === undefined) return undefined;\n\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n delete(key: K): void {\n this.cache.delete(key);\n }\n\n set(key: K, value: V): this {\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey as K);\n }\n this.cache.delete(key);\n this.cache.set(key, value);\n return this;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n keys(): IterableIterator {\n return this.cache.keys();\n }\n\n values(): IterableIterator {\n return this.cache.values();\n }\n}\n","/** Mapping from outcome label to probability mass or damage attribution. */\nexport type OutcomeLabelMap = Partial>;\n\n/** Computational epsilon for pruning negligible probabilities. */\nexport const EPS = 1e-12;\n\n/** A probability bin for a specific damage value. */\nexport interface Bin {\n /** Total probability mass at this damage value. */\n p: number;\n /** Per-outcome probability mass contributions at this damage. */\n count: OutcomeLabelMap;\n /** Optional per-outcome damage attribution at this damage. */\n attr?: OutcomeLabelMap;\n}\n\nexport interface CritConfig {\n critThreshold: number;\n}\n\n/** Simple mapping from damage value to probability. */\nexport type DamageDistribution = Record;\n/** Canonical outcome labels supported by the query helpers. */\nexport type OutcomeType =\n | \"crit\"\n | \"hit\"\n | \"missNone\"\n | \"missDamage\"\n | \"saveHalf\"\n | \"saveFail\"\n | \"pc\";\n\nexport type Rounding = \"none\" | \"floor\" | \"round\" | \"ceil\";\n\n/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */\nexport type RollType = \"flat\" | \"advantage\" | \"disadvantage\" | \"elven accuracy\";\n\n/**\n * P(critical hit) for the given crit window and d20 {@link RollType}.\n *\n * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for\n * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage\n * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps\n * the worst of two.\n */\nexport function critProbability(critRange: number, rollType: RollType = \"flat\"): number {\n const base = critRange / 20;\n switch (rollType) {\n case \"advantage\":\n return 1 - (1 - base) ** 2;\n case \"elven accuracy\":\n return 1 - (1 - base) ** 3;\n case \"disadvantage\":\n return base ** 2;\n case \"flat\":\n default:\n return base;\n }\n}\n\n/**\n * The canonical \"clean miss\" outcome — a point of zero damage with no rider.\n * This is the {@link OutcomeType} that attribution charts and outcome stats key\n * on, and is distinct from the builder's attack-resolution `miss` weight label.\n */\nexport const MISS_NONE_OUTCOME: OutcomeType = \"missNone\";\n\n/**\n * All outcome types in canonical severity order — clean miss → crit. This is\n * also the natural stacking order for attribution charts (least- to\n * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly\n * once; use it instead of hand-maintained per-consumer outcome tables.\n */\nexport const ALL_OUTCOME_TYPES: OutcomeType[] = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n];\n\n/**\n * Outcome types in display order for stats / breakdown rows — most prominent\n * first (crit, hit, …) down to the clean miss.\n */\nexport const OUTCOME_DISPLAY_ORDER: OutcomeType[] = [\n \"crit\",\n \"hit\",\n \"missDamage\",\n \"saveHalf\",\n \"saveFail\",\n \"pc\",\n \"missNone\",\n];\n\n/**\n * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}).\n * Labels not present in `order` sort after known ones, alphabetically — so\n * ad-hoc/test labels outside the {@link OutcomeType} union stay stable.\n */\nexport function sortOutcomes(\n outcomes: Iterable,\n order: readonly string[] = ALL_OUTCOME_TYPES\n): T[] {\n const rank = new Map(order.map((o, i) => [o, i]));\n return [...outcomes].sort((a, b) => {\n const ra = rank.get(a);\n const rb = rank.get(b);\n if (ra !== undefined && rb !== undefined) return ra - rb;\n if (ra !== undefined) return -1;\n if (rb !== undefined) return 1;\n return a.localeCompare(b);\n });\n}\n\nexport const onAnyHit: OutcomeType[] = [\"hit\", \"crit\"];\nexport const onCritOnly: OutcomeType[] = [\"crit\"];\nexport const onHitOnly: OutcomeType[] = [\"hit\"];\nexport const onMissOnly: OutcomeType[] = [\"missNone\", \"missDamage\"];\nexport const onMissDamageOnly: OutcomeType[] = [\"missDamage\"];\nexport const onSaveHalfOnly: OutcomeType[] = [\"saveHalf\"];\nexport const onSaveFailOnly: OutcomeType[] = [\"saveFail\"];\nexport const onPotentCantripOnly: OutcomeType[] = [\"pc\"];\n","import type { OutcomeType } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/**\n * Query interface for analyzing dice roll probability distributions.\n *\n * Combines multiple attack PMFs and provides statistical analysis methods for:\n * - Basic statistics (mean, variance, min/max, percentiles)\n * - Probability queries (hit chances, success rates, exact counts)\n * - Damage analysis (ranges by outcome type, expected values)\n * - Data export (charts, tables, visualizations)\n *\n */\n\nexport class DiceQuery {\n public readonly singles: PMF[];\n private readonly _eps: number;\n private readonly _combinedProvided: boolean;\n private _combined?: PMF;\n private _combinedWithAttr?: PMF;\n\n constructor(singles: PMF | PMF[], combined?: PMF, eps = EPS) {\n this.singles = Array.isArray(singles) ? singles : [singles];\n if (this.singles.some((s) => s === undefined)) {\n throw new Error(\"DiceQuery contains undefined singles\");\n }\n this._eps = eps;\n // When the caller supplies an explicit combined distribution that may not\n // equal convolve(singles), the additive closed-form moments would describe\n // a different distribution than cdf/percentiles. Track this so mean()/\n // variance() can fall back to the provided combined and stay consistent.\n this._combinedProvided = combined !== undefined;\n if (combined !== undefined) {\n this._combined =\n Math.abs(combined.mass() - 1) <= eps ? combined : combined.normalize();\n }\n }\n\n /**\n * The combined damage distribution of all single PMFs (their convolution),\n * normalized to total probability 1.\n *\n * Computed lazily on first access and cached. Queries that only need\n * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},\n * {@link DiceQuery.stddev} — never trigger this convolution.\n */\n get combined(): PMF {\n if (this._combined === undefined) {\n const c = PMF.convolveMany(this.singles);\n this._combined =\n Math.abs(c.mass() - 1) <= this._eps ? c : c.normalize();\n }\n return this._combined;\n }\n\n private static readonly DEFAULT_OUTCOMES: readonly OutcomeType[] = [\n \"hit\",\n \"crit\",\n \"missNone\",\n ] as const;\n\n /**\n * Returns a new PMF with damage attribution metadata populated.\n *\n * This method computes attribution on-demand for builder-generated PMFs,\n * enabling them to work with damage attribution charts. The `attr` field\n * tracks how much damage each outcome type contributes at each damage value.\n *\n * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)\n *\n * Performance: Cached after first call. Adds minimal overhead vs `combined`.\n *\n * @returns PMF with attr field populated for damage attribution charts\n *\n * @example\n * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))\n * const query = attack.toQuery()\n * const pmf = query.combinedWithAttribution()\n * // Now pmf can be used with toDamageAttributionChartSeries()\n */\n combinedWithAttribution(): PMF {\n if (this._combinedWithAttr) {\n return this._combinedWithAttr;\n }\n\n // Fast path: if every single already carries attribution (as parser-\n // generated PMFs do), the attributed convolution is bit-for-bit identical\n // to `combined` — reuse it instead of convolving a second time.\n if (this.singles.every((pmf) => pmf.hasAttribution())) {\n this._combinedWithAttr = this.combined;\n return this._combinedWithAttr;\n }\n\n // Otherwise (e.g. builder-generated PMFs that only carry `count`), add\n // attribution to each single PMF, then convolve.\n const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());\n const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);\n\n // Normalize if needed\n const normalized =\n Math.abs(combined.mass() - 1) <= this.combined.epsilon\n ? combined\n : combined.normalize();\n\n this._combinedWithAttr = normalized;\n return normalized;\n }\n\n /**\n * Per-label `damage value → probability mass` series for the combined,\n * attribution-carrying distribution — the provenance core of the stacked\n * damage-attribution chart. Convenience for\n * `combinedWithAttribution().attributionByValue()`; see\n * {@link PMF.attributionByValue}.\n */\n attributionByValue(): Map> {\n return this.combinedWithAttribution().attributionByValue();\n }\n\n /**\n * How many of the independent single PMFs can produce the given outcome\n * label. Useful for \"all of them succeeded\" style probabilities where the\n * exponent is the number of contributing attacks (see\n * {@link DiceQuery.probExactlyK}).\n */\n countSinglesWith(label: string): number {\n let count = 0;\n for (const single of this.singles) {\n if (single.hasOutcome(label)) count++;\n }\n return count;\n }\n\n /**\n * Returns the expected damage across all possible outcomes.\n *\n * Example: `query.mean()` → 12.5\n * Use case: \"What's my average damage per round?\"\n */\n mean(): number {\n // If the caller supplied a combined distribution that may diverge from\n // convolve(singles), report its mean so mean() stays consistent with\n // cdf/percentiles/min/max (which all read `combined`).\n if (this._combinedProvided) {\n let m = 0;\n for (const [damageValue, bin] of this.combined) m += damageValue * bin.p;\n return m;\n }\n // Expectation is additive over independent attacks: E[Σ Xᵢ] = Σ E[Xᵢ].\n // Computing it directly from the singles avoids building `combined`.\n let totalMean = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n // Match `combined`'s semantics: only divide by mass when it is not\n // already 1 (within eps), so an already-normalized single is bit-exact.\n totalMean +=\n Math.abs(mass - 1) <= this._eps ? single.mean() : single.mean() / mass;\n }\n return totalMean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n *\n * Example: `query.variance()` → 45.2\n * Use case: \"How much does my damage vary from the average?\"\n * High variance means higher risk/reward. Lower variance means more consistent damage.\n */\n variance(): number {\n // Consistent with mean(): if a (possibly divergent) combined was provided,\n // compute the variance of that distribution directly.\n if (this._combinedProvided) {\n const mu = this.mean();\n let v = 0;\n for (const [damageValue, bin] of this.combined) {\n const dev = damageValue - mu;\n v += dev * dev * bin.p;\n }\n return v;\n }\n // Variance is additive over independent attacks: Var[Σ Xᵢ] = Σ Var[Xᵢ].\n // Use the centered form per single (E[(X−μ)²]) rather than E[X²]−μ², which\n // suffers catastrophic cancellation when damage has a large constant offset.\n let totalVariance = 0;\n for (const single of this.singles) {\n const mass = single.mass();\n if (mass <= 0) continue;\n if (Math.abs(mass - 1) <= this._eps) {\n // PMF.variance() is the centered, cached, mass-1 variance.\n totalVariance += single.variance();\n } else {\n // Normalized centered variance of a non-unit-mass single.\n let mu = 0;\n for (const [d, b] of single) mu += d * (b.p / mass);\n let v = 0;\n for (const [d, b] of single) {\n const dev = d - mu;\n v += dev * dev * (b.p / mass);\n }\n totalVariance += v;\n }\n }\n return totalVariance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n *\n * Example: `query.stdev()` → 6.7\n * Use case: \"What's the typical spread around my average damage?\"\n * Used to determine how consistent the damage is.\n */\n stddev(): number {\n return Math.sqrt(this.variance());\n }\n\n /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */\n stdev(): number {\n return this.stddev();\n }\n\n /**\n * Returns the Cumulative Distribution Function.\n */\n cdf(x: number): number {\n return this.probTotalAtMost(x);\n }\n\n /**\n * Returns the probability of dealing X damage or less.\n * In statistics, this is called the cumulative distribution function (CDF).\n * Example: `query.cdf(20)` → 0.75\n * Use case: \"What's the chance I deal 20 damage or less?\"\n */\n probTotalAtMost(x: number): number {\n let cumulativeProbability = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue <= x) {\n cumulativeProbability += probabilityBin.p;\n }\n }\n return cumulativeProbability;\n }\n\n /**\n * Returns the Complementary Cumulative Distribution Function.\n */\n ccdf(x: number): number {\n return this.probTotalAtLeast(x);\n }\n\n /**\n * Returns the probability of dealing at least X damage.\n *\n * Example: `query.probTotalAtLeast(25)` → 0.35\n * Use case: \"What's the chance I deal at least 25 damage to finish the enemy?\"\n */\n probTotalAtLeast(threshold: number): number {\n let probabilitySum = 0;\n for (const [damageValue, probabilityBin] of this.combined) {\n if (damageValue >= threshold) {\n probabilitySum += probabilityBin.p;\n }\n }\n return probabilitySum;\n }\n\n /**\n * Returns damage values at specific percentiles.\n *\n * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18]\n * Use case: \"What are my 25th, 50th, and 75th percentile damage values?\"\n */\n percentiles(percentileValues: number[]): number[] {\n const sortedDamageValues = this.combined.support();\n if (sortedDamageValues.length === 0) return percentileValues.map(() => 0);\n\n const cumulativeProbabilities: number[] = [];\n let runningProbabilitySum = 0;\n for (const damageValue of sortedDamageValues) {\n runningProbabilitySum += this.combined.map.get(damageValue)!.p;\n cumulativeProbabilities.push(runningProbabilitySum);\n }\n\n return percentileValues.map((targetPercentile) => {\n // Binary search for efficiency\n let leftBound = 0;\n let rightBound = cumulativeProbabilities.length - 1;\n\n while (leftBound <= rightBound) {\n const middleIndex = Math.floor((leftBound + rightBound) / 2);\n if (cumulativeProbabilities[middleIndex] >= targetPercentile) {\n rightBound = middleIndex - 1;\n } else {\n leftBound = middleIndex + 1;\n }\n }\n\n return leftBound < sortedDamageValues.length\n ? sortedDamageValues[leftBound]\n : sortedDamageValues[sortedDamageValues.length - 1];\n });\n }\n\n /**\n * Returns the minimum possible damage.\n *\n * Example: `query.min()` → 0\n * Use case: \"What's the worst-case damage if everything misses?\"\n */\n min(): number {\n return this.combined.min();\n }\n\n /**\n * Returns the maximum possible damage.\n *\n * Example: `query.max()` → 56\n * Use case: \"What's the best-case damage if everything crits and rolls max?\"\n */\n max(): number {\n return this.combined.max();\n }\n\n private singleProb(diceIndex: number, label: OutcomeType): number {\n const single = this.singles[diceIndex];\n let probabilitySum = 0;\n for (const [, probabilityBin] of single) {\n probabilitySum += probabilityBin.count[label] || 0;\n }\n // Return the conditional per-event probability. Dividing by the single's\n // mass keeps the result in [0,1] for a non-normalized single (e.g. one that\n // has been scaled), so probAtLeastOne / the binomial DP stay mass-invariant.\n const mass = single.mass();\n return mass > 0 ? probabilitySum / mass : 0;\n }\n\n /**\n * Full count distribution [P(0), P(1), …, P(n)] for \"an attack succeeds if it\n * carries ANY of `labels`\", over the n independent singles.\n *\n * Each single's per-event success probability is the Poisson-binomial\n * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),\n * computed exactly once. The binomial DP then runs once to produce the whole\n * distribution, so the array-label paths of probExactlyK / probAtLeastK /\n * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and\n * re-running the DP per requested k.\n */\n private countDistribution(labels: OutcomeType[]): number[] {\n const n = this.singles.length;\n const successProbabilities = this.singles.map((single) =>\n new DiceQuery([single]).probabilityOf(labels)\n );\n\n const dist = new Array(n + 1).fill(0);\n dist[0] = 1;\n for (const successProb of successProbabilities) {\n for (let outcomeCount = n; outcomeCount >= 1; outcomeCount--) {\n dist[outcomeCount] =\n dist[outcomeCount] * (1 - successProb) +\n dist[outcomeCount - 1] * successProb;\n }\n dist[0] *= 1 - successProb;\n }\n return dist;\n }\n\n probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number {\n const L = Array.isArray(labels) ? [...new Set(labels)] : [labels];\n const n = this.singles.length;\n\n if (k <= 0) return 1;\n if (k > n) return 0;\n\n // Sum the upper tail of the single, shared count distribution rather than\n // calling probExactlyK (which rebuilt the distribution) once per i.\n const dist = this.countDistribution(L);\n let tail = 0;\n for (let i = k; i <= n; i++) {\n tail += dist[i];\n }\n\n if (tail < 0) return 0;\n if (tail > 1) return 1;\n return tail;\n }\n\n /**\n * Returns the probability that at least one attack has the specified outcome(s).\n * - This is the complement of probAtMostK(labels, 0)\n *\n * Examples:\n * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits)\n * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds)\n *\n * Use cases:\n * - \"What's the chance at least one of my attacks connects?\"\n *\n * Note:\n *\n * - You have to pass in an array of labels to avoid double-counting if you are\n * using multiple labels. You cannot just add them.\n */\n probAtLeastOne(labels: OutcomeType | OutcomeType[]): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n labels = [labels];\n }\n\n let productOfNonOccurrence = 1;\n for (let diceIndex = 0; diceIndex < this.singles.length; diceIndex++) {\n // Calculate total probability of any of the specified labels occurring.\n // Clamp to [0,1] so floating-point drift in the per-attack sum cannot push\n // the complement out of range.\n let combinedProbability = 0;\n for (const label of labels) {\n combinedProbability += this.singleProb(diceIndex, label);\n }\n if (combinedProbability < 0) combinedProbability = 0;\n else if (combinedProbability > 1) combinedProbability = 1;\n productOfNonOccurrence *= 1 - combinedProbability;\n }\n const result = 1 - productOfNonOccurrence;\n return result < 0 ? 0 : result > 1 ? 1 : result;\n }\n\n /**\n * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label.\n *\n * Uses dynamic programming to efficiently calculate the probability distribution\n * of how many attacks will have the specified outcome, accounting for different\n * success probabilities across individual attacks.\n *\n * Example: For 3 attacks with 50% hit chance each, returns:\n * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)]\n *\n * @param label - The outcome type to count\n * @param maxK - Maximum number of occurrences to calculate (usually number of attacks)\n * @returns Array where index K contains P(exactly K attacks have the label)\n */\n private computeBinomialProbabilities(\n label: OutcomeType,\n maxK: number\n ): number[] {\n const individualProbabilities = this.singles.map((_, diceIndex) =>\n this.singleProb(diceIndex, label)\n );\n const binomialProbs = new Array(maxK + 1).fill(0);\n binomialProbs[0] = 1;\n\n for (const singleProbability of individualProbabilities) {\n for (let outcomeCount = maxK; outcomeCount >= 1; outcomeCount--) {\n binomialProbs[outcomeCount] =\n binomialProbs[outcomeCount] * (1 - singleProbability) +\n binomialProbs[outcomeCount - 1] * singleProbability;\n }\n binomialProbs[0] *= 1 - singleProbability;\n }\n\n return binomialProbs;\n }\n\n /**\n * Returns the probability that exactly K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probExactlyK('hit', 2) = probability exactly 2 attacks hit\n * - probExactlyK('crit', 1) = probability exactly 1 attack crits\n * - probExactlyK('crit', 0) = probability no attacks crit\n *\n * Array examples:\n * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed\n * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds\n * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss\n *\n * Use cases:\n * - \"What's the chance exactly one of my attacks hits?\"\n * - \"How likely am I to get exactly 2 successes out of 3 attacks?\"\n * - \"What's the probability that exactly half my attacks succeed?\"\n *\n * Note: For arrays, an attack counts as a \"success\" if it has any of the specified labels.\n * This is different from probAtMostK, which counts an attack as a \"success\" if it has ALL of the specified labels.\n */\n probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n return probabilityArray[k];\n }\n\n // For multiple labels, derive P(exactly k) from the single shared count\n // distribution. (k > n is impossible, so index out of range reads as 0.)\n const dist = this.countDistribution(labels);\n return k >= 0 && k < dist.length ? dist[k] : 0;\n }\n\n /**\n * Returns the probability that AT MOST K attacks result in the specified outcome(s).\n *\n * Single label examples:\n * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)\n * - probAtMostK('crit', 0) = probability no attacks crit\n * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss\n *\n * Array examples:\n * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds\n * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss)\n *\n * Use cases:\n * - \"What's the chance that at most one attack hits?\" (rest miss)\n * - \"How likely am I to have mostly failures?\" (at most 1 success)\n * - \"What's the probability of a really bad turn?\" (at most 0 successes)\n *\n */\n probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number {\n // Handle single label case (backward compatibility)\n if (typeof labels === \"string\") {\n const probabilityArray = this.computeBinomialProbabilities(labels, k);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= k; outcomeCount++) {\n cumulativeSum += probabilityArray[outcomeCount];\n }\n return cumulativeSum;\n }\n\n // For multiple labels, sum the lower tail of the single shared count\n // distribution rather than recomputing it per outcomeCount.\n const dist = this.countDistribution(labels);\n const upper = Math.min(k, dist.length - 1);\n let cumulativeSum = 0;\n for (let outcomeCount = 0; outcomeCount <= upper; outcomeCount++) {\n cumulativeSum += dist[outcomeCount];\n }\n return cumulativeSum;\n }\n\n /**\n * Returns the expected damage attributed to specific outcome types.\n *\n * Single label examples:\n * - expectedDamageFrom('hit') = expected damage from hit components\n * - expectedDamageFrom('crit') = expected damage from crit components\n *\n * Array examples:\n * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success\n * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses\n *\n * Use cases:\n * - \"How much damage do I expect from successful attacks?\"\n * - \"What's the damage contribution from critical hits specifically?\"\n * - \"How much damage comes from miss effects (like save-for-half spells)?\"\n */\n expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number {\n const wanted = Array.isArray(labels) ? labels : [labels];\n\n let total = 0;\n // By linearity of expectation, we can sum the expected damages from each individual PMF. This avoids issues with the `count` aggregation during\n for (const single of this.singles) {\n for (const [dmg, bin] of single) {\n let p = 0;\n for (const label of wanted) p += bin.count[label] ?? 0;\n total += dmg * p;\n }\n }\n return total;\n }\n\n /**\n * Returns damage statistics for scenarios where AT LEAST ONE attack results in\n * the specified outcome(s).\n *\n * This method answers \"What happens when things go reasonably well?\" rather than\n * \"What's the theoretical maximum?\" It includes mixed scenarios which are more\n * common and tactically relevant than pure scenarios.\n *\n * Single label examples:\n * - damageStatsFrom('hit') = damage range when at least one attack hits\n * - damageStatsFrom('crit') = damage range when at least one attack crits\n *\n * Array examples:\n * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds\n * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses\n *\n * Tactical Use Cases:\n * - \"Given that I don't completely whiff (99% of turns), what damage should I expect?\"\n * - \"When planning to kill a 60 HP enemy, what's my damage range on successful turns?\"\n * - \"Should I use this risky spell if it has good damage when it works?\"\n * - \"What's my damage potential when something goes right?\" (vs pure failure)\n *\n * Combat Planning Examples:\n * - 4 attacks with 90% hit chance: \"96% of the time you'll do 25-150 damage, avg 52\"\n * (Much more useful than \"You average 50 damage including complete misses\")\n * - Risk assessment: \"80% of successful turns do 40-80 damage, but 20% do 80-150\"\n * - Resource management: \"If I hit anything, I'll likely finish this enemy\"\n *\n * Statistical Note:\n * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which\n * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.\n *\n * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an\n * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and\n * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than\n * E[dmg | the label occurs]. For a single attack both are the plain\n * conditional figures. Use {@link probAtLeastOne} for the scenario probability.\n *\n * @example\n * // High-level tactical planning\n * const successStats = query.damageStatsFrom('hit')\n * const successChance = query.probAtLeastOne('hit')\n * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`)\n */\n damageStatsFrom(labels: OutcomeType | OutcomeType[]): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Normalize input to array for uniform handling\n const labelArray = typeof labels === \"string\" ? [labels] : labels;\n\n let minDamage = Infinity;\n let maxDamage = -Infinity;\n let totalDamage = 0;\n let totalCount = 0;\n\n for (const [damage, probabilityBin] of this.combined) {\n // Check if this bin has any of the specified labels\n let binHasAnyLabel = false;\n let binContribution = 0;\n\n for (const label of labelArray) {\n const count = probabilityBin.count[label] as number;\n if (count && count > 0) {\n binHasAnyLabel = true;\n binContribution += count;\n }\n }\n\n if (damage > 0 && binHasAnyLabel) {\n minDamage = Math.min(minDamage, damage);\n maxDamage = Math.max(maxDamage, damage);\n\n // For single labels, use the specific count; for multiple labels, use total probability\n const weightToUse =\n labelArray.length === 1 ? binContribution : probabilityBin.p;\n totalDamage += damage * weightToUse;\n totalCount += weightToUse;\n }\n }\n\n return {\n min: minDamage === Infinity ? 0 : minDamage,\n max: maxDamage === -Infinity ? 0 : maxDamage,\n avg: totalCount > 0 ? totalDamage / totalCount : 0,\n count: totalCount,\n };\n }\n\n /**\n * Returns damage statistics for scenarios where ALL attacks result in the specified\n * outcome, calculated by leveraging the pure partition of singles.\n *\n * This method answers \"What's the theoretical best/worst case?\" and \"What are the\n * clean mathematical boundaries?\" It provides pure scenarios without mixing outcomes.\n *\n * Examples:\n * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss)\n * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit)\n *\n * UI and Display Use Cases:\n * - Statistics panels showing \"MAX Hit Damage\" (users expect pure hits, not mixed)\n * - \"Best case scenario\" vs \"worst case scenario\" analysis\n * - Mathematical verification: \"Does our hit damage calculation match manual math?\"\n * - Clean damage type attribution: \"How much comes from base hits vs crits?\"\n *\n * Design and Balance Use Cases:\n * - Game designers: \"What's the damage ceiling if someone gets lucky?\"\n * - Character optimization: \"What's my absolute maximum potential?\"\n * - Ability comparison: \"Which build has higher crit ceiling?\"\n * - Minimum guaranteed damage: \"What's the worst I can do if everything hits?\"\n *\n * Mathematical Use Cases:\n * - Validating complex calculations against simple manual math\n * - Understanding damage component contributions in isolation\n * - Separating luck (crit variance) from consistency (hit variance)\n * - Building intuition about damage sources\n *\n * When to Use This vs damageStatsFrom():\n * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons\n * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios\n *\n * Statistical Note:\n * Pure scenarios (all hits, all crits) are rare but represent clear mathematical\n * boundaries. These stats help understand the \"shape\" of your damage potential.\n *\n * @example\n * // UI display logic\n * const pureHitMax = query.combinedDamageStats('hit').max // Clean \"MAX Hit Damage: 90\"\n * const pureCritMax = query.combinedDamageStats('crit').max // Clean \"MAX Crit Damage: 168\"\n *\n * // vs tactical planning (use damageStatsFrom instead)\n * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios\n */\n combinedDamageStats(targetLabel: OutcomeType): {\n min: number;\n max: number;\n avg: number;\n count: number;\n } {\n // Get pure statistics from each single attack (singles are already pure partitions)\n const singleStats = this.singles.map((single) =>\n new DiceQuery([single]).damageStatsFrom(targetLabel)\n );\n\n // If any single attack has no outcomes of this type, return zeros\n if (singleStats.some((stats) => stats.count === 0)) {\n return { min: 0, max: 0, avg: 0, count: 0 };\n }\n\n // Calculate combined statistics for N attacks all of target type\n const combinedMin = singleStats.reduce((sum, stats) => sum + stats.min, 0);\n const combinedMax = singleStats.reduce((sum, stats) => sum + stats.max, 0);\n const combinedAvg = singleStats.reduce((sum, stats) => sum + stats.avg, 0);\n const combinedProb = singleStats.reduce(\n (product, stats) => product * stats.count,\n 1\n );\n\n return {\n min: combinedMin,\n max: combinedMax,\n avg: combinedAvg,\n count: combinedProb,\n };\n }\n\n /**\n * Returns the probability that at least one attack carries ANY of the\n * specified labels (the marginal P(≥1) across the independent attacks).\n *\n * Examples:\n * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)\n * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success)\n *\n * Use cases:\n * - \"What's the chance my resolution includes a success label?\"\n * - \"How likely am I to get any hits or crits across all attacks?\"\n *\n * Note: this must NOT be computed by summing `combined` bin probabilities. A\n * single combined damage total is reachable by many outcome combinations and\n * a bin can hold several labels at once, so summing `bin.p` over bins that\n * contain a label over-counts. The correct marginal is the Poisson-binomial\n * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.\n */\n probabilityOf(labels: OutcomeType | OutcomeType[]): number {\n return this.probAtLeastOne(labels);\n }\n\n /**\n * Returns the probability of missing (any type of miss).\n *\n * Example: `query.missChance()` → 0.04\n * Use case: \"What's the chance I miss completely this turn?\"\n */\n missChance(): number {\n // Miss can be either explicit misses with damage or zero-damage misses\n return this.probabilityOf([\"missDamage\", \"missNone\"]);\n }\n\n /**\n * Returns data formatted for plotting damage probability distribution.\n *\n * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...]\n * Use case: \"I want to visualize my damage distribution in a chart.\"\n */\n toChartSeries(): Array<{ x: number; y: number }> {\n return this.combined.support().map((damageValue) => ({\n x: damageValue,\n y: this.combined.map.get(damageValue)!.p,\n }));\n }\n\n /**\n * Returns tabular data showing damage values and their probability breakdowns.\n *\n * Example: `query.toLabeledTable(['hit', 'crit'])` →\n * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...]\n *\n * Use case: \"I want to see exactly how hit/crit probabilities contribute to each damage value.\"\n */\n toLabeledTable(\n labels: OutcomeType[] = []\n ): Array<{ damage: number; total: number } & Record> {\n return this.combined.support().map((damageValue) => {\n const probabilityBin = this.combined.map.get(damageValue)!;\n const tableRow: { damage: number; total: number } & Record<\n string,\n number\n > = {\n damage: damageValue,\n total: probabilityBin.p,\n };\n for (const outcomeLabel of labels) {\n tableRow[outcomeLabel] = probabilityBin.count[outcomeLabel] || 0;\n }\n return tableRow;\n });\n }\n\n /**\n * Returns data for stacked charts with unconditional per-label probability mass per damage.\n *\n * - Each dataset value equals the unconditional probability mass for that label at that damage\n * (i.e., `bin.count[label]`).\n * - Column sums may be less than the total probability `bin.p` when you omit labels or when\n * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match.\n * - This behavior matches tests that expect raw per-label mass (not proportional scaling).\n * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more.\n *\n * @example\n * query.toStackedChartData(['hit', 'crit'])\n * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}\n */\n toStackedChartData(\n labels: OutcomeType[] = [],\n epsilon = EPS\n ): { labels: number[]; datasets: Array<{ label: string; data: number[] }> } {\n const damageValues = this.combined.support();\n\n const datasets = labels.map((outcomeLabel) => ({\n label: outcomeLabel,\n data: damageValues.map((dmg) => {\n const bin = this.combined.map.get(dmg);\n const v = bin ? (bin.count[outcomeLabel] as number) || 0 : 0;\n return v <= epsilon ? 0 : v;\n }),\n }));\n\n return { labels: damageValues, datasets };\n }\n\n /**\n * Returns pure mathematical data for attribution charts showing outcome contributions.\n *\n * Automatically discovers all outcome types present in the PMF, applies filtering rules,\n * and returns proportional data suitable for stacked visualization.\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and proportional data\n *\n * @example\n * query.toAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}}\n */\n toAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Calculate proportion only among outcomes that will actually be charted\n const outcomeCount = (bin.count[outcome] as number) || 0;\n\n // Calculate total count excluding outcomes that will be filtered out\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n\n if (totalChartableCount === 0) return 0;\n\n // Calculate what fraction of chartable probability comes from this outcome\n const outcomeFraction = outcomeCount / totalChartableCount;\n const outcomeProbability = bin.p * outcomeFraction;\n\n return asPercentages ? outcomeProbability * 100 : outcomeProbability;\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for damage attribution charts showing damage contribution\n * from each outcome type at each damage value.\n *\n * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of\n * bin.count (probability attribution).\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+)\n * @returns Pure data structure with support, outcomes, and damage attribution data\n *\n * @example\n * query.toDamageAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}}\n */\n toDamageAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from attr, not count)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n if (bin.attr) {\n for (const outcomeType in bin.attr) {\n if (bin.attr[outcomeType] && (bin.attr[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n // If both are in stackOrder, use that order\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n\n // Put unknowns at the end\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n\n // Both unknown, alphabetical\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin || !bin.attr) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Get the damage attribution for this outcome at this damage value\n const outcomeDamageAttribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value (for percentage calculation)\n let totalDamageAttribution = 0;\n for (const [outcomeName, damageAttr] of Object.entries(bin.attr)) {\n if (filterRules(outcomeName, damage)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // Calculate the damage attribution percentage\n const damagePercentage =\n (outcomeDamageAttribution / totalDamageAttribution) * 100;\n\n // Scale by the probability of this damage value occurring\n // This makes bar height proportional to P(damage = x) while preserving attribution percentages\n return damagePercentage * bin.p * 100; // Scale by probability, *100 to make visible\n } else {\n return outcomeDamageAttribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for outcome attribution charts showing which\n * attack outcome combinations can produce each damage value.\n *\n * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks\n * outcome combinations - answering \"what attack outcomes produced this damage?\"\n *\n * @param options Configuration options\n * @param options.stackOrder Preferred order for outcome types (unknowns placed at end)\n * @param options.filterRules Function to determine if outcome should be included for a given damage value\n * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support, outcomes, and outcome combination probabilities\n *\n * @example\n * query.toOutcomeAttributionChartSeries()\n * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}}\n */\n toOutcomeAttributionChartSeries(\n options: {\n stackOrder?: string[];\n filterRules?: (outcome: string, damage: number) => boolean;\n asPercentages?: boolean;\n } = {}\n ): {\n support: number[];\n outcomes: string[];\n data: { [outcome: string]: number[] };\n } {\n const {\n stackOrder = [\n \"missNone\",\n \"missDamage\",\n \"saveFail\",\n \"saveHalf\",\n \"pc\",\n \"hit\",\n \"crit\",\n ],\n filterRules = (outcome: string, damage: number) =>\n !(outcome === \"missNone\" && damage !== 0),\n asPercentages = true,\n } = options;\n\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], outcomes: [], data: {} };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Find ALL outcome types that exist in the PMF (from count, for outcome tracking)\n const allOutcomeTypes = new Set();\n for (const [, bin] of this.combined.map) {\n for (const outcomeType in bin.count) {\n if (bin.count[outcomeType] && (bin.count[outcomeType] as number) > 0) {\n allOutcomeTypes.add(outcomeType);\n }\n }\n }\n\n // Sort existing outcomes by preferred stack order, with unknowns at end\n const existingOutcomes = Array.from(allOutcomeTypes).sort((a, b) => {\n const indexA = stackOrder.indexOf(a);\n const indexB = stackOrder.indexOf(b);\n\n if (indexA >= 0 && indexB >= 0) return indexA - indexB;\n if (indexA >= 0) return -1;\n if (indexB >= 0) return 1;\n return a.localeCompare(b);\n });\n\n if (existingOutcomes.length === 0) {\n return { support, outcomes: [], data: {} };\n }\n\n // Calculate data for each outcome type\n const data: { [outcome: string]: number[] } = {};\n\n for (const outcome of existingOutcomes) {\n data[outcome] = support.map((damage) => {\n const bin = this.combined.map.get(damage);\n if (!bin) return 0;\n\n // Apply filter rules\n if (!filterRules(outcome, damage)) {\n return 0;\n }\n\n // Handle missNone specially since it doesn't appear in bin.attr (0 damage)\n if (outcome === \"missNone\") {\n // For missNone, use bin.count since it contributes 0 damage\n const outcomeCount = (bin.count[outcome] as number) || 0;\n if (outcomeCount === 0) return 0;\n\n if (asPercentages) {\n // For missNone, show probability proportion since damage attribution would be 0\n let totalChartableCount = 0;\n for (const [outcomeName, count] of Object.entries(bin.count)) {\n if (filterRules(outcomeName, damage)) {\n totalChartableCount += (count as number) || 0;\n }\n }\n if (totalChartableCount === 0) return 0;\n const outcomeFraction = outcomeCount / totalChartableCount;\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeCount;\n }\n }\n\n // Use damage attribution to calculate proportional contribution for non-missNone outcomes\n if (!bin.attr) return 0;\n\n const outcomeDamageContribution = (bin.attr[outcome] as number) || 0;\n\n if (asPercentages) {\n // Calculate total damage attribution at this damage value\n let totalDamageAttribution = 0;\n for (const [, damageAttr] of Object.entries(bin.attr)) {\n totalDamageAttribution += (damageAttr as number) || 0;\n }\n\n if (totalDamageAttribution === 0) return 0;\n\n // The proportion is: damage from this outcome / total damage from all outcomes\n // This gives us exactly what you want: for damage 13 = hit(11) + miss(2),\n // hit proportion = 11/13, miss proportion = 2/13\n const outcomeFraction =\n outcomeDamageContribution / totalDamageAttribution;\n\n // Scale by probability for bar height while preserving damage attribution percentages\n return outcomeFraction * bin.p * 100;\n } else {\n return outcomeDamageContribution;\n }\n });\n }\n\n return {\n support,\n outcomes: existingOutcomes,\n data,\n };\n }\n\n /**\n * Returns pure mathematical data for cumulative distribution function (CDF).\n * Shows P(X ≤ x) - the probability of getting at most x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and cumulative probabilities\n *\n * @example\n * query.toCDFSeries()\n * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]}\n */\n toCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n let cumulativeProbability = 0;\n const cdfData: number[] = [];\n\n for (const damage of support) {\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n cdfData.push(\n asPercentages ? cumulativeProbability * 100 : cumulativeProbability\n );\n }\n\n return {\n support,\n data: cdfData,\n };\n }\n\n /**\n * Returns pure mathematical data for complementary cumulative distribution function (CCDF).\n * Shows P(X ≥ x) - the probability of getting at least x damage.\n *\n * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1)\n * @returns Pure data structure with support and complementary cumulative probabilities\n *\n * @example\n * query.toCCDFSeries()\n * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]}\n */\n toCCDFSeries(asPercentages: boolean = true): {\n support: number[];\n data: number[];\n } {\n const originalSupport = this.combined.support();\n if (originalSupport.length === 0) {\n return { support: [], data: [] };\n }\n\n // Create complete integer range from min to max\n const minDamage = Math.min(...originalSupport);\n const maxDamage = Math.max(...originalSupport);\n const support = Array.from(\n { length: maxDamage - minDamage + 1 },\n (_, i) => minDamage + i\n );\n\n // Calculate CCDF: P(X ≥ x) = 1 - P(X < x)\n let cumulativeProbability = 0;\n const ccdfData: number[] = [];\n\n for (const damage of support) {\n // For CCDF at point x, we want P(X ≥ x) = 1 - P(X < x)\n // which is the total probability minus cumulative up to (but not including) x\n const ccdf = 1 - cumulativeProbability;\n ccdfData.push(asPercentages ? ccdf * 100 : ccdf);\n\n // Now add current probability for next iteration\n const bin = this.combined.map.get(damage);\n if (bin) {\n cumulativeProbability += bin.p;\n }\n }\n\n return {\n support,\n data: ccdfData,\n };\n }\n\n /*\n Statistics snapshot of the query.\n */\n\n /** Probability of doing strictly more than threshold damage (default >0). */\n probDamageGreaterThan(threshold = 0): number {\n let acc = 0;\n for (const [x, bin] of this.combined.map) if (x > threshold) acc += bin.p;\n return acc;\n }\n\n /** All outcome keys actually present (typed & ordered if you pass an order). */\n outcomeKeys(order?: OutcomeType[]): OutcomeType[] {\n const found = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count)\n if (bin.count[k] && (bin.count[k] as number) > 0) found.add(k);\n }\n if (found.size === 0)\n [\"hit\", \"crit\", \"missNone\"].forEach((k) => found.add(k));\n const keys = Array.from(found).filter(\n (k) => order?.includes(k as OutcomeType) ?? true\n ) as OutcomeType[];\n if (order && order.length)\n keys.sort((a, b) => order.indexOf(a) + 999 - (order.indexOf(b) + 999));\n return keys;\n }\n\n /** Total probability per outcome across the PMF. */\n outcomeTotals(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n const totals = new Map();\n outcomes.forEach((o) => totals.set(o, 0));\n for (const [, row] of this.combined.map) {\n for (const o of outcomes) {\n const p = (row.count[o] as number) || 0; // if your bins store per-outcome p; else derive via toLabeledTable\n totals.set(o, (totals.get(o) || 0) + p);\n }\n }\n return totals;\n }\n\n /** Conditional damage range per outcome (min/avg/max of X | outcome). */\n outcomeDamageRanges(\n outcomes: OutcomeType[] = this.outcomeKeys()\n ): Map {\n // Use toLabeledTable to stay consistent with your existing attribution\n const table = this.toLabeledTable(outcomes);\n const ranges = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n outcomes.forEach((o) => ranges.set(o, { sum: 0, mass: 0 }));\n\n for (const row of table) {\n const dmg = row.damage as number;\n for (const o of outcomes) {\n const p = (row[o] as number) || 0; // joint mass at (damage, outcome)\n if (p > 0) {\n const r = ranges.get(o)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n }\n\n const out = new Map<\n OutcomeType,\n { min: number; avg: number; max: number }\n >();\n for (const o of outcomes) {\n const r = ranges.get(o)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n out.set(o, { min: r.min ?? 0, avg, max: r.max ?? 0 });\n }\n return out;\n }\n\n /**\n * Snapshot of the distribution in the exact shape the UI consumes.\n * - outcome probabilities are \"at least one\" (and equal to \"all\" for a single PMF)\n * - damageRange is conditional on the outcome occurring\n *\n * The outcome probabilities use the correct Poisson-binomial marginals\n * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),\n * so they are always valid probabilities in [0,1].\n *\n * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from\n * the combined PMF's `count`, which the convolution accumulates as an EXPECTED\n * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]\n * rather than a clean conditional expectation. It is correct for a single\n * attack.\n */\n snapshot(order?: readonly OutcomeType[]): Snapshot {\n // 1) Discover which outcomes actually appear in this PMF\n const discovered = new Set();\n for (const [, bin] of this.combined.map) {\n for (const k in bin.count) {\n if (bin.count[k] && (bin.count[k] as number) > 0) discovered.add(k);\n }\n }\n if (discovered.size === 0) {\n for (const k of DiceQuery.DEFAULT_OUTCOMES) discovered.add(k);\n }\n\n let outcomes = Array.from(discovered);\n\n // Optional filtering + ordering if a preferred order was provided\n if (order && order.length) {\n const inOrder = new Set(order);\n outcomes = outcomes.filter((k) => inOrder.has(k as OutcomeType));\n const rank = new Map(order.map((k, i) => [k, i]));\n outcomes.sort(\n (a, b) =>\n (rank.get(a as OutcomeType) ?? 999) -\n (rank.get(b as OutcomeType) ?? 999)\n );\n }\n\n // 2) Aggregate per-outcome mass and conditional damage ranges via labeled table\n const rows = this.toLabeledTable(outcomes as OutcomeType[]);\n\n const rangeAcc = new Map<\n OutcomeType,\n { min?: number; max?: number; sum: number; mass: number }\n >();\n for (const ot of outcomes) {\n rangeAcc.set(ot as OutcomeType, { sum: 0, mass: 0 });\n }\n\n for (const row of rows) {\n const dmg = row.damage as number;\n for (const ot of outcomes) {\n const p = (row[ot] as number) || 0;\n if (p <= 0) continue;\n\n const r = rangeAcc.get(ot as OutcomeType)!;\n r.sum += dmg * p;\n r.mass += p;\n if (r.min === undefined || dmg < r.min) r.min = dmg;\n if (r.max === undefined || dmg > r.max) r.max = dmg;\n }\n }\n\n const n = this.singles.length;\n const outcomeMap = new Map();\n for (const ot of outcomes) {\n const r = rangeAcc.get(ot as OutcomeType)!;\n const avg = r.mass > 0 ? r.sum / r.mass : 0;\n // Use the correct per-attack marginals (Poisson-binomial) so these are\n // always valid probabilities in [0,1]. For a single attack both reduce to\n // P(outcome); the previous `total` was an expected count and could exceed 1.\n outcomeMap.set(ot as OutcomeType, {\n atLeastOneProbability: this.probAtLeastOne(ot as OutcomeType),\n allProbability: this.probAtLeastK(ot as OutcomeType, n),\n damageRange: { min: r.min ?? 0, avg, max: r.max ?? 0 },\n });\n }\n\n // 3) Scalars: mean, damageChance, and percentiles from the dense CDF\n const averageDPR = this.mean();\n\n let damageChance = 0; // P(total damage > 0)\n for (const [x, bin] of this.combined.map) if (x > 0) damageChance += bin.p;\n\n const { support, data } = this.toCDFSeries(false); // P(X ≤ x) in 0..1\n const quantile = (p: number) => {\n if (support.length === 0) return 0;\n for (let i = 0; i < support.length; i++)\n if (data[i] >= p) return support[i];\n return support[support.length - 1];\n };\n const percentiles = {\n p25: quantile(0.25),\n p50: quantile(0.5),\n p75: quantile(0.75),\n };\n\n return { averageDPR, damageChance, percentiles, outcomes: outcomeMap };\n }\n\n /**\n * PMF Transformation Methods\n *\n * These methods provide a fluent API for transforming dice queries by wrapping\n * the underlying PMF transformation methods. All operations work on the combined\n * PMF and return new DiceQuery instances.\n */\n\n /**\n * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0).\n *\n * @returns New DiceQuery with normalized combined PMF\n */\n normalize(): DiceQuery {\n return new DiceQuery([this.combined.normalize()]);\n }\n\n /**\n * Returns a new DiceQuery with low-probability outcomes removed.\n *\n * @param eps Minimum probability threshold (defaults to PMF epsilon)\n * @param keepFinalBin Whether to keep the highest damage bin regardless of probability\n * @returns New DiceQuery with compacted combined PMF\n */\n compact(eps?: number, keepFinalBin?: boolean): DiceQuery {\n return new DiceQuery([this.combined.compact(eps, keepFinalBin)]);\n }\n\n /**\n * Returns a new DiceQuery with an additional scaled branch added.\n * Useful for conditional outcomes like \"30% chance of opportunity attack\".\n *\n * @param branch DiceQuery to add as a scaled branch\n * @param probability Probability of the branch occurring (0-1)\n * @returns New DiceQuery combining this query with the scaled branch\n *\n * @example\n * const baseAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const opportunityAttack = parse(\"(d20 + 5 AC 15) * (1d8 + 3)\");\n * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3);\n */\n addScaled(branch: DiceQuery, probability: number): DiceQuery {\n return new DiceQuery([\n this.combined.addScaled(branch.combined, probability),\n ]);\n }\n\n /**\n * Returns a new DiceQuery with all probabilities scaled by a factor.\n * Used for conditional scenarios where the entire outcome has reduced probability.\n *\n * @param factor Scaling factor for probabilities\n * @returns New DiceQuery with scaled probabilities\n *\n * @example\n * const fullAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario\n */\n scaleMass(factor: number): DiceQuery {\n return new DiceQuery([this.combined.scaleMass(factor)]);\n }\n\n totalMass(): number {\n return this.combined.mass();\n }\n\n /**\n * Returns a new DiceQuery with damage values transformed by a function.\n * Useful for applying modifiers, resistances, or other damage transformations.\n *\n * @param damageTransformFunction Function to transform each damage value\n * @returns New DiceQuery with transformed damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage\n * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage\n */\n mapDamage(\n damageTransformFunction: (damageValue: number) => number\n ): DiceQuery {\n return new DiceQuery([this.combined.mapDamage(damageTransformFunction)]);\n }\n\n /**\n * Returns a new DiceQuery with damage values scaled by a factor.\n * Convenient wrapper around mapDamage for multiplicative scaling.\n *\n * @param factor Scaling factor for damage values\n * @param rounding Rounding method: \"floor\" (default), \"round\", or \"ceil\"\n * @returns New DiceQuery with scaled damage values\n *\n * @example\n * const baseAttack = parse(\"2d6 + 3\");\n * const doubled = baseAttack.scaleDamage(2); // Double damage\n * const halfDamage = baseAttack.scaleDamage(0.5, \"round\"); // Half damage, rounded\n */\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): DiceQuery {\n return new DiceQuery([this.combined.scaleDamage(factor, rounding)]);\n }\n\n /**\n * Returns a new DiceQuery combining this query with another via convolution.\n * Equivalent to rolling both queries independently and adding results.\n * It is important to use this rather than combing()ing the PMFs directly!\n * This method maintains the provenance of the PMFs which is needed for damage attribution.\n * Combining the .combined PMFs directly is still valid for DPR calculations but\n * is not statistically sound for queries.\n *\n * @param other DiceQuery to combine with\n * @param eps Optional epsilon for precision control\n * @returns New DiceQuery representing the combined outcome\n *\n * @example\n * const mainAttack = parse(\"(d20 + 5 AC 15) * (2d6 + 3)\");\n * const bonusAttack = parse(\"(d20 + 3 AC 15) * (1d6 + 1)\");\n * const bothAttacks = mainAttack.convolve(bonusAttack);\n */\n convolve(other: DiceQuery): DiceQuery {\n const singles = [...this.singles, ...other.singles];\n return new DiceQuery(singles);\n }\n\n /**\n * First-success split over an ordered list of DISTINCT single-swing PMFs.\n * Each PMF may have different success/subset probabilities (from labels).\n *\n * successOutcome: e.g., [\"success\"] or [\"hit\", \"crit\"]\n * subsetOutcome: e.g., [\"subset\"] or [\"crit\"] where subset ⊆ success\n *\n * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]\n */\n public firstSuccessSplit(\n successOutcome: OutcomeType | OutcomeType[],\n subsetOutcome: OutcomeType | OutcomeType[],\n eps = EPS\n ): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number] {\n const pmfs = this.singles;\n if (!pmfs.length) {\n throw new Error(\"firstSuccessSplitFromPMFs: pmfs must be non-empty\");\n }\n\n const toArr = (x: OutcomeType | OutcomeType[]): OutcomeType[] =>\n Array.isArray(x) ? x : [x];\n const clamp01 = (x: number) => Math.max(0, Math.min(1, x));\n const tol = Math.max(eps, 8 * Number.EPSILON);\n\n // Per-event probabilities from each PMF via DiceQuery([pmf])\n const per = pmfs.map((pmf) => {\n const dq = new DiceQuery([pmf]);\n const pS = dq.probAtLeastOne(toArr(successOutcome));\n const pB = dq.probAtLeastOne(toArr(subsetOutcome));\n if (pB - pS > eps) {\n throw new Error(\n \"firstSuccessSplitFromPMFs: P(subset) > P(success) for an event. Ensure subset ⊆ success.\"\n );\n }\n return { pS, pB };\n });\n\n // Aggregate with running miss prefix\n let missSoFar = 1;\n let pFirstSubset = 0;\n let pFirstNonSubset = 0;\n let pNone = 1;\n\n for (const { pS, pB } of per) {\n pFirstSubset += missSoFar * pB;\n pFirstNonSubset += missSoFar * (pS - pB);\n const miss = 1 - pS;\n missSoFar *= miss;\n pNone *= miss;\n }\n\n const pAny = 1 - pNone;\n\n // Clamp and sanity check\n const a = clamp01(pFirstNonSubset);\n const b = clamp01(pFirstSubset);\n const any = clamp01(pAny);\n const none = clamp01(pNone);\n\n if (Math.abs(a + b - any) > tol * Math.max(1, any)) {\n throw new Error(\n `firstSuccessSplitFromPMFs: parts do not sum to pAny. got a+b=${\n a + b\n }, pAny=${any}`\n );\n }\n\n return [a, b, any, none] as const;\n }\n}\n// Make sure these types are exported in your public index, or inline them here.\nexport type OutcomeSnapshot = {\n atLeastOneProbability: number; // P(outcome occurs at least once)\n allProbability: number; // equal to atLeastOneProbability for a single aggregated PMF\n damageRange: { min: number; avg: number; max: number }; // conditional on the outcome occurring\n};\n\nexport type Snapshot = {\n averageDPR: number;\n damageChance: number; // P(total damage > 0)\n percentiles: { p25: number; p50: number; p75: number };\n outcomes: Map;\n};\n","import { LRUCache } from \"../common/lru-cache\";\nimport type { Bin, OutcomeLabelMap, Rounding } from \"../common/types\";\nimport { EPS, MISS_NONE_OUTCOME } from \"../common/types\";\nimport { DiceQuery } from \"./query\";\n\nconst cacheEnabled = true;\n\nexport const pmfCache = new LRUCache(1000);\n\n/**\n * Probability Mass Function for discrete damage distributions.\n */\nexport class PMF {\n // Unique ID generator for anonymous PMFs to avoid cache key collisions\n private static __anonIdCounter = 1;\n\n // Cached computed values\n private _support?: number[];\n private _min?: number;\n private _max?: number;\n private _totalMass?: number;\n private _mean?: number;\n private _variance?: number;\n private _stdev?: number;\n private _fingerprint?: string;\n\n constructor(\n public readonly map: Map = new Map(),\n public readonly epsilon = EPS,\n public readonly normalized = false,\n public readonly identifier: string = `anon#${PMF.__anonIdCounter++}`,\n private _preservedProvenance = true\n ) {}\n\n static empty(epsilon = EPS, identifier = \"empty\") {\n return new PMF(new Map(), epsilon, false, identifier);\n }\n\n // This has a single bin at value 0, mass of 1\n static zero(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { miss: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"zero\");\n }\n\n static delta(value: number, epsilon = EPS): PMF {\n return PMF.fromMap(new Map([[value, 1]]), epsilon);\n }\n\n /**\n * Point mass at damage 0 tagged with the canonical `missNone` outcome.\n *\n * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the\n * builder's attack-resolution vocabulary. This uses the `missNone`\n * {@link OutcomeType} that the attribution charts and outcome stats key on,\n * so it is the correct \"clean miss / no damage\" delta for provenance-aware\n * mixtures feeding those consumers.\n */\n static missNone(epsilon = EPS): PMF {\n const m = new Map();\n m.set(0, { p: 1, count: { [MISS_NONE_OUTCOME]: 1 }, attr: {} });\n return new PMF(m, epsilon, false, \"missNone\");\n }\n\n // This creates a single bin at value 0, but with weight 0.\n static emptyMass(): PMF {\n return PMF.zero().scaleMass(0);\n }\n\n // Makes PMF iterable over [damage, bin] pairs.\n [Symbol.iterator](): IterableIterator<[number, Bin]> {\n return this.map[Symbol.iterator]();\n }\n\n static clearCache() {\n pmfCache.clear();\n }\n\n /**\n * Creates a conditional PMF from two branches (success and failure) and a probability.\n * This is the core logic for modeling any probabilistic event where there are two\n * distinct outcomes.\n */\n static branch(\n successPMF: PMF,\n failurePMF: PMF,\n successProbability: number\n ): PMF {\n let p = successProbability;\n if (!Number.isFinite(p)) p = 0;\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n\n const q = 1 - p;\n\n // Fast paths. scaleMass(1) returns the same instance, so these hand back the\n // branch PMF unchanged. That is safe because PMFs are treated as immutable\n // (compact() and the other transforms now clone bins rather than mutate).\n if (p === 0) return failurePMF.scaleMass(1);\n if (p === 1) return successPMF.scaleMass(1);\n\n // Choose epsilon. You can also pick Math.min for a tighter threshold.\n const eps = successPMF.epsilon ?? failurePMF.epsilon;\n const id = `branch(${failurePMF.identifier}*${q.toFixed(6)} + ${\n successPMF.identifier\n }*${p.toFixed(6)})`;\n\n // Proper Bernoulli mixture: q·failure ⊕ p·success, assembled in a single\n // pass. The previous `empty().addScaled(failure,q).addScaled(success,p)`\n // chain copied failurePMF's bins twice (into the intermediate, then again\n // when the intermediate was copied by the second addScaled). Merging both\n // scaled branches directly into one fresh map keeps the same accumulation\n // order — q·failure first, then p·success — so the result is bit-identical.\n const resultMap = new Map();\n for (const [damageValue, bin] of failurePMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, q));\n }\n for (const [damageValue, bin] of successPMF.map) {\n PMF.mergeInto(resultMap, damageValue, PMF.scaleBin(bin, p));\n }\n\n return new PMF(resultMap, eps, false, id);\n }\n\n /**\n * withProbability()\n *\n * A convenience wrapper around branch() for the common case where the \"failure\" branch is always zero().\n *\n * Think of this as a shortcut for:\n * pmf.gate(p, PMF.zero())\n *\n * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't,\n * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses.\n *\n * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios.\n * - For multiple independent swings, use DiceQuery with separate PMFs for each attack.\n * - For modeling \"first success\" logic across multiple attacks (like Sneak Attack or Smite)\n * use query.firstSuccessSplit() to get the exact probabilities.\n * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive().\n *\n */\n static withProbability(successPMF: PMF, probability: number): PMF {\n return PMF.branch(successPMF, PMF.zero(), probability);\n }\n\n /**\n * gate()\n *\n * A conditional wrapper around branch() that applies this PMF with probability `p`,\n * and applies a provided fallback PMF otherwise.\n *\n * This is useful for modeling a binary choice between two outcomes:\n * - The \"success\" outcome (this PMF) happens with probability `p`.\n * - The \"failure\" outcome (fallback PMF) happens with probability `1 - p`.\n *\n * Examples:\n * - 25% chance to include an opportunity attack, otherwise nothing:\n * attackPMF.gate(0.25, PMF.zero())\n *\n * - 50% chance to deal fireball damage, otherwise cone of cold damage:\n * fireballPMF.gate(0.5, coneOfColdPMF)\n *\n * Relationship to other helpers:\n * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`.\n * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none).\n *\n * @param p Probability of applying this PMF (between 0 and 1).\n * @param fallback PMF to apply when this PMF is *not* selected.\n * @returns A new PMF representing the weighted mixture of this PMF and the fallback.\n */\n gate(p: number, fallback: PMF) {\n return PMF.branch(this, fallback, p);\n }\n\n /**\n * PMF.exclusive()\n *\n * Builds a single PMF from a set of mutually exclusive weighted outcomes.\n * Exactly one of the provided options will occur.\n *\n * Each option has:\n * - A PMF representing its outcome (e.g., damage dice).\n * - A weight representing its probability of being selected.\n *\n * Notes:\n * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for floating point rounding.\n */\n static exclusive(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights\n for (const { weight } of items) {\n if (!Number.isFinite(weight) || weight < -eps) {\n throw new Error(`PMF.exclusive: invalid weight ${weight}.`);\n }\n }\n\n // Sum and check\n let totalWeight = items.reduce((s, { weight }) => s + weight, 0);\n\n // Normalize tiny negatives to 0 and tiny overshoot to 1 when within eps\n if (Math.abs(totalWeight) <= eps) totalWeight = 0;\n if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;\n\n if (totalWeight > 1 + EPS) {\n throw new Error(\n `PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`\n );\n }\n\n // Accumulate scaled components, skipping near-zero weights\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (weight > eps) out = out.addScaled(pmf, weight);\n }\n\n // Add leftover mass at zero outcome\n const leftover = Math.max(0, 1 - totalWeight);\n if (leftover > eps) {\n out = out.addScaled(PMF.zero(), leftover);\n }\n\n return out;\n }\n\n /**\n * PMF.mix()\n *\n * Builds a PMF as a linear combination of input PMFs with the given weights.\n * Unlike `exclusive`, this does NOT:\n * - enforce that weights sum to 1\n * - add leftover probability to δ0 (PMF.zero())\n *\n * Use when outcomes are not mutually exclusive, or for interpolation/blending.\n *\n * @param options Array of `{ pmf, weight }` or `[PMF, number]`.\n * @param eps Optional tolerance for skipping tiny weights.\n */\n static mix(\n options: Array<{ pmf: PMF; weight: number } | [PMF, number]>,\n eps = EPS\n ): PMF {\n const items = options.map((o) =>\n Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o\n );\n\n // Validate weights, but do not constrain their sum.\n for (const { weight } of items) {\n if (!Number.isFinite(weight)) {\n throw new Error(`PMF.mix: invalid weight ${weight}.`);\n }\n }\n\n let out = PMF.empty(eps);\n for (const { pmf, weight } of items) {\n if (Math.abs(weight) <= eps) continue; // ignore crumbs\n out = out.addScaled(pmf, weight);\n }\n return out;\n }\n /**\n * Adds damage attribution metadata to this PMF based on existing count metadata.\n * For each bin, sets attr[outcome] = damage × count[outcome].\n *\n * This enables damage attribution charts to work with builder-generated PMFs.\n * The parser generates attr automatically, but builder PMFs only have count.\n *\n * @returns New PMF with attr field populated in each bin\n */\n /**\n * Returns true if this PMF already carries damage attribution metadata.\n *\n * Only the first positive-damage bin is inspected (parser-generated PMFs\n * populate `attr` uniformly), so this is O(1) in practice.\n */\n hasAttribution(): boolean {\n for (const [damage, bin] of this.map) {\n if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {\n return true;\n }\n // Only check the first non-zero bin for performance\n if (damage > 0) break;\n }\n return false;\n }\n\n withAttribution(): PMF {\n // Fast path: if attr already exists, return this PMF unchanged\n if (this.hasAttribution()) return this;\n\n const newMap = new Map();\n\n for (const [damage, bin] of this.map) {\n const attr: OutcomeLabelMap = {};\n\n // For each outcome type in count, compute its damage contribution\n for (const outcome in bin.count) {\n const probability = bin.count[outcome] as number;\n if (probability > 0) {\n attr[outcome] = damage * probability;\n }\n }\n\n // Create new bin with attribution\n newMap.set(damage, {\n p: bin.p,\n count: { ...bin.count },\n attr: Object.keys(attr).length > 0 ? attr : undefined,\n });\n }\n\n // Use a different identifier to avoid cache collisions with non-attributed version\n return new PMF(\n newMap,\n this.epsilon,\n this.normalized,\n `${this.identifier}~attr`\n );\n }\n\n /**\n * General-purpose N-way mixture.\n * weights: Array of [weight, PMF].\n *\n * Example: PMF.mixN([\n * [pMiss, zero],\n * [pHit, hitPMF],\n * [pCrit, critPMF],\n * ]);\n */\n static mixN(weights: [number, PMF][], eps = EPS): PMF {\n // Treat tiny/negative as zero; keep performance clean\n const filtered = weights.filter(([w]) => w > eps);\n\n if (filtered.length === 0) {\n return PMF.emptyMass(); // not PMF.zero(): we want \"no mass\" mixture\n }\n\n // No need to normalize up front; we accumulate and blend by relative weight\n let acc: PMF | null = null;\n let sum = 0;\n\n for (const [w, pmf] of filtered) {\n if (acc === null) {\n acc = pmf;\n sum = w;\n } else {\n const q = w / (sum + w); // relative weight of the new component\n acc = PMF.branch(pmf, acc, q); // success=new pmf, failure=acc\n sum += w;\n }\n }\n\n // If everything got filtered out (all ~0), return empty mass\n return acc ?? PMF.emptyMass();\n }\n\n // This is a convenience method for when we use power\n // TODO: It can be smarter in the future, and we can also add it to query\n // That way statistics operations on invalid PMFs can throw an error\n // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?\n private setPreservedProvenance(preserved: boolean) {\n if (!this._preservedProvenance && preserved) {\n throw new Error(\n \"Preserved provenance is already set to false, cannot fix that\"\n );\n }\n this._preservedProvenance = preserved;\n }\n\n public preservedProvenance(): boolean {\n return this._preservedProvenance;\n }\n\n private getPowerCacheKey(n: number, eps: number): string {\n const id = this.identifier;\n let key = `${id}`;\n for (let i = 1; i < n; i++) key += `+${id}`;\n return `${key}@${eps}`;\n }\n\n /**\n * Efficiently computes this PMF convolved with itself `n` times.\n * Uses exponentiation by squaring to reduce total convolutions.\n * n must be a positive integer.\n * *\n * * NOTE: This folds multiple independent attacks into a single PMF.\n * As a result, The power() method causes a loss of data provenance.\n * This is ONLY SAFE if you are trying to calculate masses.\n * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power().\n */\n power(n: number, eps = this.epsilon): PMF {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"power(n): n must be a positive integer\");\n }\n if (n === 1) return this;\n\n const epsilon = eps ?? this.epsilon;\n\n const key = this.getPowerCacheKey(n, epsilon);\n if (cacheEnabled) {\n const cached = pmfCache?.get(key);\n if (cached) return cached;\n }\n\n // Start from the base PMF and accumulate n-1 additional powers\n let base: PMF = this.normalized ? this : this.normalize();\n let result: PMF = base;\n let exp = n - 1;\n\n while (exp > 0) {\n if (exp & 1) {\n result = result.convolve(base, epsilon);\n }\n exp >>= 1;\n if (exp > 0) {\n base = base.convolve(base, epsilon);\n }\n }\n\n result.setPreservedProvenance(false);\n if (cacheEnabled) {\n pmfCache?.set(key, result);\n }\n return result;\n }\n\n /*\n * Helper for chaining multiple identical attacks\n */\n replicate(n: number): PMF[] {\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\"replicate(n): n must be a positive integer\");\n }\n if (n === 1) return [this];\n return Array.from({ length: n }, () => this);\n }\n\n mass(): number {\n if (this._totalMass === undefined) {\n let totalProbabilityMass = 0;\n for (const { p } of this.map.values()) {\n totalProbabilityMass += p;\n }\n this._totalMass = totalProbabilityMass;\n }\n return this._totalMass;\n }\n\n outcomeMass(outcome: string): number {\n let totalProbabilityMass = 0;\n for (const { p, count } of this.map.values()) {\n totalProbabilityMass += p * ((count[outcome] as number) ?? 0);\n }\n return totalProbabilityMass;\n }\n\n // Helper for testing\n faceTotal(): number {\n return [...this.map.keys()].reduce((sum, key) => sum + key, 0);\n }\n\n normalize(): PMF {\n if (this.normalized) return this;\n const normalizationFactor = this.mass();\n if (normalizationFactor === 0) return this;\n\n // Note: this divides by normalizationFactor rather than multiplying by its\n // reciprocal, to keep results bit-identical to direct division.\n const normalizedMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n const normalizedCount: OutcomeLabelMap = {};\n for (const labelKey in probabilityBin.count) {\n normalizedCount[labelKey] =\n (probabilityBin.count[labelKey] as number) / normalizationFactor;\n }\n\n let normalizedAttributes: OutcomeLabelMap | undefined;\n if (probabilityBin.attr) {\n normalizedAttributes = {};\n for (const labelKey in probabilityBin.attr) {\n normalizedAttributes[labelKey] =\n (probabilityBin.attr[labelKey] as number) / normalizationFactor;\n }\n }\n\n normalizedMap.set(damageValue, {\n p: probabilityBin.p / normalizationFactor,\n count: normalizedCount,\n attr: normalizedAttributes,\n });\n }\n return new PMF(normalizedMap, this.epsilon, true, this.identifier);\n }\n\n /**\n * Returns a copy with negligible probabilities removed (p < eps).\n * If keepFinalBin is true, the bin with the largest key is always kept,\n * even if its probability is below eps. count/attr submaps are still cleaned.\n */\n compact(eps = this.epsilon, keepFinalBin = false): PMF {\n let maxKey = -Infinity;\n if (keepFinalBin) {\n for (const key of this.map.keys()) {\n if (key > maxKey) maxKey = key;\n }\n }\n\n const compactedMap = new Map();\n\n for (const [damageValue, probabilityBin] of this.map) {\n const shouldKeep =\n probabilityBin.p >= eps || (keepFinalBin && damageValue === maxKey);\n\n if (!shouldKeep) continue;\n\n // Build a fresh Bin rather than mutating the source. Bins are shared by\n // reference across PMFs (e.g. branch()/addScaled()/scaleMass() fast paths\n // can carry another PMF's bin objects), so deleting sub-eps entries in\n // place would silently corrupt the source PMF's count/attr.\n const cleanedBin = PMF.cloneBin(probabilityBin);\n\n for (const labelKey in cleanedBin.count) {\n if (Math.abs(cleanedBin.count[labelKey] || 0) < eps) {\n delete cleanedBin.count[labelKey];\n }\n }\n\n if (cleanedBin.attr) {\n for (const labelKey in cleanedBin.attr) {\n if (Math.abs(cleanedBin.attr[labelKey] || 0) < eps) {\n delete cleanedBin.attr[labelKey];\n }\n }\n if (Object.keys(cleanedBin.attr).length === 0) {\n cleanedBin.attr = undefined;\n }\n }\n\n compactedMap.set(damageValue, cleanedBin);\n }\n\n return new PMF(compactedMap, eps, this.normalized, this.identifier);\n }\n\n // Note: The \"support\" of a PMF is the set of all non-zero probability outcomes.\n // This returns all damage values with non-zero probability, sorted ascending.\n support(): number[] {\n if (this._support === undefined) {\n this._support = [...this.map.keys()].sort((a, b) => a - b);\n }\n return this._support!;\n }\n\n // Minimum possible damage value.\n min(): number {\n if (this._min === undefined) {\n const support = this.support();\n this._min = support.length > 0 ? support[0] : 0;\n }\n return this._min;\n }\n\n // Maximum possible damage value.\n max(): number {\n if (this._max === undefined) {\n const support = this.support();\n this._max = support.length > 0 ? support[support.length - 1] : 0;\n }\n return this._max;\n }\n\n /**\n * Returns the expected (mean) damage value.\n * Cached for performance since this requires iterating through all bins.\n */\n mean(): number {\n if (this._mean === undefined) {\n let totalSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n totalSum += damageValue * probabilityBin.p;\n }\n this._mean = totalSum;\n }\n return this._mean;\n }\n\n /**\n * Returns the variance of the damage distribution.\n * Cached for performance since this requires mean calculation plus iteration.\n */\n variance(): number {\n if (this._variance === undefined) {\n const meanValue = this.mean();\n let varianceSum = 0;\n for (const [damageValue, probabilityBin] of this.map) {\n const deviationFromMean = damageValue - meanValue;\n varianceSum += deviationFromMean * deviationFromMean * probabilityBin.p;\n }\n this._variance = varianceSum;\n }\n return this._variance;\n }\n\n /**\n * Returns the standard deviation of the damage distribution.\n */\n stdev(): number {\n if (this._stdev === undefined) {\n this._stdev = Math.sqrt(this.variance());\n }\n return this._stdev;\n }\n\n /** Deep-copies a Bin, cloning its count and (optional) attr maps. */\n private static cloneBin(bin: Bin): Bin {\n return {\n p: bin.p,\n count: { ...bin.count },\n attr: bin.attr ? { ...bin.attr } : undefined,\n };\n }\n\n /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */\n private static scaleBin(bin: Bin, factor: number): Bin {\n const count: OutcomeLabelMap = {};\n for (const k in bin.count) {\n count[k] = (bin.count[k] as number) * factor;\n }\n\n let attr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n attr = {};\n for (const k in bin.attr) {\n attr[k] = (bin.attr[k] as number) * factor;\n }\n }\n\n return { p: bin.p * factor, count, attr };\n }\n\n private static mergeInto(\n destinationMap: Map,\n damageValue: number,\n binToAdd: Bin\n ) {\n const existingBin = destinationMap.get(damageValue);\n if (!existingBin) {\n destinationMap.set(damageValue, PMF.cloneBin(binToAdd));\n return;\n }\n\n existingBin.p += binToAdd.p;\n\n for (const labelKey in binToAdd.count) {\n existingBin.count[labelKey] =\n (existingBin.count[labelKey] || 0) +\n (binToAdd.count[labelKey] as number);\n }\n\n if (binToAdd.attr) {\n if (!existingBin.attr) {\n existingBin.attr = {};\n }\n for (const labelKey in binToAdd.attr) {\n existingBin.attr[labelKey] =\n (existingBin.attr[labelKey] || 0) +\n (binToAdd.attr[labelKey] as number);\n }\n }\n }\n\n // Convenience method\n add(other: PMF): PMF {\n return this.addScaled(other, 1);\n }\n\n /**\n * Returns a new PMF with a scaled branch added to this one.\n * The branch PMF is scaled by the given probability before merging\n * This will be very useful for conditional effects and for being\n * able to model \"I can probably have this opportunity attack 40% of rounds\"\n * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes\n */\n addScaled(branch: PMF, probability: number): PMF {\n if (probability === 0) return this;\n\n const resultMap = new Map();\n for (const [dmg, bin] of this.map) {\n resultMap.set(dmg, PMF.cloneBin(bin));\n }\n\n for (const [damageValue, probabilityBin] of branch.map) {\n PMF.mergeInto(\n resultMap,\n damageValue,\n PMF.scaleBin(probabilityBin, probability)\n );\n }\n\n return new PMF(\n resultMap,\n this.epsilon,\n false,\n `${this.identifier}+scaled(${branch.identifier},${probability})`\n );\n }\n\n /**\n * Redistributes probability mass to model an effect that only occurs with\n * probability `frequency` — a conditional attack, an on-hit rider, or a\n * sub-one AoE target fraction.\n *\n * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass,\n * per-label `count`, AND per-label `attr` — and the freed mass is moved into\n * the miss bin at damage 0, tagged with the canonical `missNone` outcome.\n * Total probability mass is preserved.\n *\n * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage\n * attribution (`attr`) intact, so a frequency-scaled PMF still renders\n * correctly in the damage-attribution charts.\n *\n * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0`\n * collapses all mass into the miss bin. The miss outcome is assumed to be\n * encoded at damage value 0.\n *\n * @param frequency Probability in [0, 1] that the effect occurs.\n */\n applyHitFrequency(frequency: number): PMF {\n if (!Number.isFinite(frequency) || frequency >= 1) return this;\n const freq = Math.max(0, frequency);\n\n const pMiss = this.pAt(0);\n const pHit = 1 - pMiss;\n const newMissMass = pMiss + (1 - freq) * pHit;\n\n const newMap = new Map();\n newMap.set(0, {\n p: newMissMass,\n count: { [MISS_NONE_OUTCOME]: newMissMass },\n attr: {},\n });\n\n for (const [damage, bin] of this.map) {\n if (damage <= 0) continue;\n newMap.set(damage, PMF.scaleBin(bin, freq));\n }\n\n return new PMF(\n newMap,\n this.epsilon,\n false,\n `freq(${this.identifier},${freq})`\n );\n }\n\n scaleMass(factor: number): PMF {\n if (factor === 1) return this;\n\n const scaledMap = new Map();\n for (const [damageValue, probabilityBin] of this.map) {\n scaledMap.set(damageValue, PMF.scaleBin(probabilityBin, factor));\n }\n return new PMF(\n scaledMap,\n this.epsilon,\n false,\n `scale(${this.identifier},${factor})`\n );\n }\n\n mapDamage(damageTransformFunction: (damageValue: number) => number): PMF {\n const transformedMap = new Map();\n for (const [originalDamage, probabilityBin] of this.map) {\n const transformedDamage = damageTransformFunction(originalDamage);\n PMF.mergeInto(\n transformedMap,\n transformedDamage,\n PMF.cloneBin(probabilityBin)\n );\n }\n return new PMF(\n transformedMap,\n this.epsilon,\n this.normalized,\n `map(${this.identifier})`\n );\n }\n\n scaleDamage(\n factor: number,\n rounding: \"floor\" | \"round\" | \"ceil\" = \"floor\"\n ): PMF {\n const roundFunction =\n rounding === \"round\"\n ? Math.round\n : rounding === \"ceil\"\n ? Math.ceil\n : Math.floor;\n return this.mapDamage((damageValue) => roundFunction(damageValue * factor));\n }\n\n private getPMFCombineCacheKey(\n p1: PMF,\n p2: PMF,\n eps: number,\n raw: boolean\n ): string {\n const [id1, id2] = [p1.identifier, p2.identifier].sort();\n\n return `v4:${raw ? \"RAW\" : \"N\"}:${id1}+${id2}@${eps}|${p1.fingerprint()}|${p2.fingerprint()}`;\n }\n\n /**\n * A small content fingerprint (mass + bin count + face sum) so convolution\n * cache keys change if the underlying numbers do. Memoized because a PMF is\n * immutable once constructed — this avoids re-summing every key on each\n * convolve() call (including cache hits).\n */\n fingerprint(): string {\n if (this._fingerprint === undefined) {\n let faceSum = 0;\n for (const k of this.map.keys()) faceSum += k;\n this._fingerprint = `${this.mass().toFixed(12)}|${this.map.size}|${faceSum}`;\n }\n return this._fingerprint;\n }\n\n convolve(other: PMF, eps?: number, raw = false): PMF {\n const epsilon = eps ?? this.epsilon;\n\n // Normalize-by-value on non-raw path\n const norm = (x: PMF) =>\n raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();\n const A0 = norm(this);\n const B0 = norm(other);\n\n const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];\n const cacheKey = this.getPMFCombineCacheKey(A, B, epsilon, raw);\n const cached = pmfCache?.get(cacheKey);\n if (cached) return cached;\n\n // Accumulate directly into each destination bin instead of building a\n // temporary Bin per (a,b) pair and merging it. The probability channel\n // (`dest.p += ap*bp`) accumulates in the same order as before, so it is\n // bit-identical; only the per-label `count`/`attr` sums re-associate, which\n // shifts them by at most a few ULP (far below the eps pruning threshold).\n const combinedMap = new Map();\n for (const [aVal, aBin] of A.map) {\n const ap = aBin.p;\n const aCount = aBin.count;\n const aAttr = aBin.attr;\n for (const [bVal, bBin] of B.map) {\n const bp = bBin.p;\n const dmg = aVal + bVal;\n\n let dest = combinedMap.get(dmg);\n if (dest === undefined) {\n dest = { p: 0, count: {} };\n combinedMap.set(dmg, dest);\n }\n\n dest.p += ap * bp;\n\n const dc = dest.count;\n for (const k in aCount) dc[k] = (dc[k] || 0) + (aCount[k] as number) * bp;\n for (const k in bBin.count)\n dc[k] = (dc[k] || 0) + (bBin.count[k] as number) * ap;\n\n if (aAttr || bBin.attr) {\n let da = dest.attr;\n if (da === undefined) {\n da = {};\n dest.attr = da;\n }\n if (aAttr)\n for (const k in aAttr) da[k] = (da[k] || 0) + (aAttr[k] as number) * bp;\n if (bBin.attr)\n for (const k in bBin.attr)\n da[k] = (da[k] || 0) + (bBin.attr[k] as number) * ap;\n }\n }\n }\n\n let result = new PMF(\n combinedMap,\n epsilon,\n !raw,\n `${A.identifier}${raw ? \"*\" : \"+\"}${B.identifier}`\n );\n\n // Enforce mass invariant: mass(out) = (raw? A.mass():1) * (raw? B.mass():1)\n const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);\n const mGot = result.mass();\n // Guard mGot !== 0: a zero-mass operand convolves to the zero measure\n // (mass 0). Without this guard the non-raw path would scaleMass(mExp/0) =\n // scaleMass(Infinity), poisoning every bin to 0*Infinity = NaN.\n if (mExp !== 0 && mGot !== 0 && Math.abs(mGot - mExp) > epsilon) {\n result = result.scaleMass(mExp / mGot);\n }\n if (!raw && mGot !== 0 && Math.abs(result.mass() - 1) > epsilon)\n result = result.normalize();\n\n pmfCache?.set(cacheKey, result);\n return result;\n }\n\n // 3) Nice wrapper so you can call pmf.combineRaw(other)\n combineRaw(other: PMF, eps?: number): PMF {\n return this.convolve(other, eps, true);\n }\n\n // Reduce a list of PMFs by left-folding convolve() with the given eps\n private static reduceConvolveLeft(pmfList: PMF[], eps: number): PMF {\n let result = pmfList[0];\n for (let i = 1; i < pmfList.length; i++) {\n result = result.convolve(pmfList[i], eps);\n }\n return result;\n }\n\n /**\n * Convolves multiple PMFs using linear convolution with automatic caching.\n * Uses a left-to-right accumulation approach for maximum cache reuse.\n * Each convolve() call automatically uses the convolution cache for performance.\n *\n * This linear approach provides better cache hits than pairwise because:\n * - Intermediate results are more predictable and stable\n * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)\n * - Order-independent cache keys work better with consistent build patterns\n */\n static convolveMany(pmfList: PMF[], eps = EPS): PMF {\n if (pmfList.length === 0) return PMF.empty(eps);\n if (pmfList.length === 1) return pmfList[0];\n\n // Linear combination with automatic intermediate caching: each prefix\n // (A+B, (A+B)+C, ...) is a stable cache key, maximizing reuse.\n return PMF.reduceConvolveLeft(pmfList, eps);\n }\n\n /**\n * Returns a plain, JSON-serializable representation of this PMF.\n *\n * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces\n * the expected output (no double-encoding). Use {@link PMF.fromJSON} to\n * reconstruct, or {@link PMF.toJSONString} if you need the string directly.\n */\n toJSON(): {\n bins: Array<[number, Bin]>;\n normalized: boolean;\n identifier: string;\n } {\n return {\n bins: [...this.map.entries()],\n normalized: this.normalized,\n identifier: this.identifier,\n };\n }\n\n /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */\n toJSONString(): string {\n return JSON.stringify(this);\n }\n\n static fromJSON(jsonData: {\n bins: Array<[number, Bin]>;\n normalized?: boolean;\n identifier?: string;\n }) {\n return new PMF(\n new Map(jsonData.bins),\n EPS,\n !!jsonData.normalized,\n jsonData.identifier || \"fromJSON\"\n );\n }\n\n /**\n * Relative pruning with optional top-K floor.\n * Keeps bins with p >= epsRel * peak, always keeps min and max damage,\n * optionally guarantees at least `minBins` survivors by adding top-K.\n * Returns a new, non-normalized PMF.\n */\n prune(epsRel: number, minBins = 0): PMF {\n const size = this.map.size;\n if (size === 0) return this;\n\n // One pass: peak, min, max\n let peak = 0;\n let minDamage = Number.POSITIVE_INFINITY;\n let maxDamage = Number.NEGATIVE_INFINITY;\n for (const [dmg, bin] of this.map) {\n if (bin.p > peak) peak = bin.p;\n if (dmg < minDamage) minDamage = dmg;\n if (dmg > maxDamage) maxDamage = dmg;\n }\n if (peak === 0)\n return new PMF(new Map(this.map), epsRel, false, this.identifier);\n\n const thresh = epsRel * peak;\n const entries = [...this.map.entries()];\n\n // Protect endpoints\n const survivorsByDmg = new Map();\n const protect = (d: number) => {\n const b = this.map.get(d);\n if (b) survivorsByDmg.set(d, b);\n };\n protect(minDamage);\n if (maxDamage !== minDamage) protect(maxDamage);\n\n // Relative survivors\n for (const [dmg, bin] of entries) {\n if (bin.p >= thresh) survivorsByDmg.set(dmg, bin);\n }\n\n // Enforce minBins via top-K if requested\n if (minBins > 0 && survivorsByDmg.size < minBins) {\n // Sort all entries by probability desc (or replace with Quickselect for O(n))\n entries.sort((a, b) => b[1].p - a[1].p);\n for (const [dmg, bin] of entries) {\n if (!survivorsByDmg.has(dmg)) {\n survivorsByDmg.set(dmg, bin);\n if (survivorsByDmg.size >= minBins) break;\n }\n }\n }\n\n // Rebuild map, pruning tiny count/attr entries with the same threshold\n const prunedMap = new Map();\n for (const [dmg, bin] of survivorsByDmg) {\n const newCount: OutcomeLabelMap = {};\n for (const k in bin.count) {\n const v = bin.count[k] as number;\n if (Math.abs(v) >= thresh) newCount[k] = v;\n }\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr) {\n for (const k in bin.attr) {\n const v = bin.attr[k] as number;\n if (Math.abs(v) >= thresh) {\n if (!newAttr) newAttr = {};\n newAttr[k] = v;\n }\n }\n }\n prunedMap.set(dmg, { p: bin.p, count: newCount, attr: newAttr });\n }\n\n // Return non-normalized PMF\n return new PMF(prunedMap, epsRel, false, `prune(${this.identifier})`);\n }\n\n /** Probability mass at exactly x. */\n pAt(x: number): number {\n return this.map.get(x)?.p ?? 0;\n }\n\n /**\n * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`.\n * Assumes a miss is encoded as the damage-0 bin (the convention used across\n * attack/save PMFs). The dual of {@link missProbability}.\n */\n hitProbability(): number {\n return 1 - this.pAt(0);\n }\n\n /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */\n missProbability(): number {\n return this.pAt(0);\n }\n\n /**\n * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width\n * damage buckets, aggregating probability mass (and `count`/`attr`\n * provenance) into each bucket's start value. Returns this PMF unchanged when\n * its integer support already fits within `maxBuckets`.\n *\n * This is a lossy display/downsampling transform (bucket start replaces the\n * exact damage value) — use it for charting wide distributions, not for DPR\n * math.\n */\n rebin(maxBuckets: number): PMF {\n if (!(maxBuckets > 0)) return this;\n const support = this.support();\n if (support.length === 0) return this;\n const min = support[0];\n const max = support[support.length - 1];\n const range = max - min;\n if (range + 1 <= maxBuckets) return this;\n const binSize = Math.ceil((range + 1) / maxBuckets);\n return this.mapDamage((d) => min + Math.floor((d - min) / binSize) * binSize);\n }\n\n /** Dense integer support from min..max (inclusive).\n * Useful for showing empty bars in charts.\n */\n denseSupport(): number[] {\n const s = this.support();\n if (s.length === 0) return [];\n const lo = Math.min(...s),\n hi = Math.max(...s);\n return Array.from({ length: hi - lo + 1 }, (_, i) => lo + i).sort(\n (a, b) => a - b\n );\n }\n\n /** CDF at x: P(X ≤ x). */\n cdfAt(x: number): number {\n let acc = 0;\n for (const [val, bin] of this.map) if (val <= x) acc += bin.p;\n return acc;\n }\n\n /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */\n quantile(p: number): number {\n if (this.map.size === 0) return 0;\n const s = this.support().sort((a, b) => a - b);\n let acc = 0;\n for (const x of s) {\n acc += this.pAt(x);\n if (acc >= p) return x;\n }\n return s[s.length - 1];\n }\n\n /** Get outcome probability at specific damage value. */\n outcomeAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.count[outcome] as number) ?? 0;\n }\n\n /** Get all outcome types present in this PMF. */\n outcomes(): string[] {\n const outcomeSet = new Set();\n for (const [, bin] of this.map) {\n for (const outcome in bin.count) {\n if ((bin.count[outcome] as number) > 0) {\n outcomeSet.add(outcome);\n }\n }\n }\n return Array.from(outcomeSet).sort();\n }\n\n /** Get total probability of an outcome across all damage values. */\n outcomeProbability(outcome: string): number {\n let total = 0;\n for (const [, bin] of this.map) {\n total += (bin.count[outcome] as number) ?? 0;\n }\n return total;\n }\n\n /** Get damage attribution for an outcome at specific damage value. */\n outcomeAttributionAt(damage: number, outcome: string): number {\n return (this.map.get(damage)?.attr?.[outcome] as number) ?? 0;\n }\n\n /** Get all outcome data at specific damage value. */\n binAt(damage: number): {\n p: number;\n count: Record;\n attr?: Record;\n } | null {\n const bin = this.map.get(damage);\n if (!bin) return null;\n\n return {\n p: bin.p,\n count: { ...bin.count } as Record,\n attr: bin.attr ? ({ ...bin.attr } as Record) : undefined,\n };\n }\n\n /** Check if outcome exists in this PMF. */\n hasOutcome(outcome: string): boolean {\n for (const [, bin] of this.map) {\n if (((bin.count[outcome] as number) ?? 0) > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Split each damage value's probability mass across outcome labels, returning\n * per-label maps of `damage value → probability mass attributable to that\n * label`. Summing over labels at a given value recovers that value's `p`.\n *\n * Damage-bearing bins are split by `attr` weight (the share of damage each\n * outcome contributed); the clean-miss bin at 0 is split by `count` weight\n * (there is no damage to attribute). Attribution is computed on demand via\n * {@link withAttribution} when absent, so builder-generated PMFs work too.\n *\n * This is the provenance core of the stacked damage-attribution chart — the\n * caller only maps these series into its rendering format (colors, binning,\n * axis labels).\n */\n attributionByValue(): Map> {\n const src = this.hasAttribution() ? this : this.withAttribution();\n const result = new Map>();\n\n const add = (label: string, damage: number, mass: number): void => {\n if (!(mass > 0)) return;\n let series = result.get(label);\n if (!series) {\n series = new Map();\n result.set(label, series);\n }\n series.set(damage, (series.get(damage) ?? 0) + mass);\n };\n\n for (const [damage, bin] of src.map) {\n const p = bin.p || 0;\n if (p <= 0) continue;\n const isMissBin = damage === 0;\n\n // Damage-0 (clean miss): split by count, crediting the missNone label.\n if (isMissBin) {\n let totalCount = 0;\n for (const k in bin.count) totalCount += (bin.count[k] as number) || 0;\n if (totalCount > 0) {\n const c = (bin.count[MISS_NONE_OUTCOME] as number) || 0;\n add(MISS_NONE_OUTCOME, damage, (c / totalCount) * p);\n }\n continue;\n }\n\n // Damage-bearing bin: split by attribution weight.\n let totalAttr = 0;\n if (bin.attr) for (const k in bin.attr) totalAttr += (bin.attr[k] as number) || 0;\n if (bin.attr && totalAttr > 0) {\n for (const k in bin.attr) {\n if (k === MISS_NONE_OUTCOME) continue;\n add(k, damage, (((bin.attr[k] as number) || 0) / totalAttr) * p);\n }\n }\n }\n\n return result;\n }\n\n tailProbGE(t: number): number {\n let s = 0;\n for (const [x, bin] of this) {\n if (bin.p > 0 && x >= t) s += bin.p;\n }\n return s;\n }\n\n tailProbGT(t: number): number {\n let s = 0;\n for (const [x, rec] of this) {\n if (x > t) s += rec.p;\n }\n return s;\n }\n\n /**\n * Returns a new PMF containing only bins where the specified outcome has non-zero probability.\n * This creates a marginal distribution for the given outcome type, with probabilities\n * scaled to represent the unconditional mass attributable to that outcome.\n */\n filterOutcome(outcome: string): PMF {\n const filteredMap = new Map();\n\n for (const [damageValue, bin] of this.map) {\n const outcomeCount = (bin.count[outcome] as number) ?? 0;\n\n // total paths that reached this bin (sum across labels)\n const totalCount = Object.values(bin.count ?? {}).reduce(\n (a, b) => (a ?? 0) + ((b as number) ?? 0),\n 0\n );\n\n if (outcomeCount > 0 && totalCount !== undefined && totalCount > 0) {\n // proportion of this bin's mass attributable to the outcome\n const proportion = outcomeCount / totalCount;\n\n // downweight p to the unconditional mass from the outcome only\n const newP = bin.p * proportion;\n\n const newCount: OutcomeLabelMap = { [outcome]: outcomeCount };\n\n let newAttr: OutcomeLabelMap | undefined;\n if (bin.attr && bin.attr[outcome] !== undefined) {\n // If attr is a count-like accumulator, scale it too.\n // If attr is already per-outcome only, you can just carry it over.\n newAttr = { [outcome]: (bin.attr[outcome] as number) * proportion };\n }\n\n filteredMap.set(damageValue, {\n p: newP,\n count: newCount,\n attr: newAttr,\n });\n }\n }\n\n return new PMF(\n filteredMap,\n this.epsilon,\n false, // don't normalize by default\n `filter(${this.identifier},${outcome})`\n );\n }\n /**\n * Calculates probabilities for first-success outcomes across n independent attempts.\n *\n * @param pSuccess - Total probability of any success on a single attempt.\n * @param pSpecial - Probability of a specific subset of successes (e.g., critical success).\n * @param n - Number of independent attempts.\n *\n * Returns:\n * - pSpecificSuccess: Probability that the first success was of the \"special\" type\n * - pGeneralSuccess: Probability that the first success was of the non-special type\n * - pNone: Probability that no successes occurred\n * - pAny: Probability that at least one success occurred\n */\n public static firstSuccessWeights(\n pSuccess: number,\n pSpecial: number,\n n: number\n ) {\n // Preconditions: the \"special\" successes are a subset of all successes, so\n // 0 <= pSpecial <= pSuccess <= 1. Without this guard, violating inputs\n // silently produce probabilities outside [0,1].\n if (\n !Number.isFinite(pSuccess) ||\n !Number.isFinite(pSpecial) ||\n pSuccess < 0 ||\n pSuccess > 1 ||\n pSpecial < 0 ||\n pSpecial - pSuccess > EPS\n ) {\n throw new Error(\n `firstSuccessWeights: require 0 <= pSpecial <= pSuccess <= 1 (got pSuccess=${pSuccess}, pSpecial=${pSpecial})`\n );\n }\n\n const pFail = 1 - pSuccess;\n const pFailAll = Math.pow(pFail, n);\n\n // Probability of at least one success\n const pAny = 1 - pFailAll;\n\n // Avoid divide-by-zero if pSuccess is 0\n const denom = pSuccess === 0 ? 1 : pSuccess;\n\n // Breakdown of first success type\n const pSpecificSuccess = (pSpecial * pAny) / denom;\n const pGeneralSuccess = ((pSuccess - pSpecial) * pAny) / denom;\n\n const pNone = 1 - pSpecificSuccess - pGeneralSuccess; // Should equal pFailAll\n\n return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };\n }\n\n mapValues(\n f: (v: number) => number,\n eps: number = EPS,\n opts?: { rounding?: Rounding; preserveCounts?: boolean }\n ): PMF {\n const rounding = opts?.rounding ?? \"none\";\n const preserveCounts = opts?.preserveCounts ?? true;\n\n const round = (x: number) =>\n rounding === \"floor\"\n ? Math.floor(x)\n : rounding === \"ceil\"\n ? Math.ceil(x)\n : rounding === \"round\"\n ? Math.round(x)\n : x;\n\n // Accumulate probs and merged counts\n const probs = new Map();\n const counts = new Map>();\n\n for (const [v, bin] of this) {\n if (Math.abs(bin.p) < eps) continue;\n const u = round(f(v));\n probs.set(u, (probs.get(u) ?? 0) + bin.p);\n\n if (preserveCounts) {\n // Merge counts if present\n const src = bin.count;\n if (src) {\n const dest = counts.get(u) ?? {};\n for (const k in src) {\n dest[k] = (dest[k] ?? 0) + (src[k] as number);\n }\n counts.set(u, dest);\n }\n }\n }\n\n // Build PMF with merged counts, then normalize\n const internal = new Map();\n for (const [u, p] of probs) {\n internal.set(u, { p, count: counts.get(u) ?? {} });\n }\n // Normalize via pmfFromMap to keep one source of truth\n return PMF.fromMap(\n new Map(Array.from(internal, ([u, b]) => [u, b.p] as [number, number])),\n eps\n );\n }\n\n static fromMap(\n m: Map,\n eps: number = EPS,\n { requireIntegerValues = true }: { requireIntegerValues?: boolean } = {}\n ): PMF {\n const filtered: Array<[number, number]> = [];\n for (const [v, p] of m) {\n if (!Number.isFinite(v) || !Number.isFinite(p)) continue;\n if (p <= 0 || Math.abs(p) < eps) continue;\n if (requireIntegerValues && !Number.isInteger(v)) {\n throw new Error(`fromMap: non-integer outcome ${v}`);\n }\n filtered.push([v, p]);\n }\n\n if (filtered.length === 0) {\n throw new Error(\"fromMap: empty or invalid input map\");\n }\n\n // Kahan sum for stability\n let sum = 0;\n let c = 0;\n for (const [, p] of filtered) {\n const y = p - c;\n const t = sum + y;\n c = t - sum - y;\n sum = t;\n }\n if (sum <= 0) throw new Error(\"pmfFromMap: probabilities sum to 0\");\n\n filtered.sort((a, b) => a[0] - b[0]);\n\n const internal = new Map();\n for (const [v, p] of filtered) {\n internal.set(v, { p: p / sum, count: {} }); // keep count object present for consistency\n }\n return new PMF(internal, eps);\n }\n\n query(): DiceQuery {\n return new DiceQuery(this);\n }\n}\n","// dice.ts (internal)\n\nimport { DiceParseError } from \"../common/errors\";\nimport type {\n Bin,\n DamageDistribution,\n OutcomeLabelMap,\n OutcomeType,\n} from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"../pmf/pmf\";\n\n/**\n * Work budget for a single dice×dice operation. binaryOp is O(faces₁ × faces₂),\n * so two individually-legal large dice (e.g. `d100000 + d100000`) would otherwise\n * enumerate ~10^10 face pairs and hang. The parser's per-die / per-count caps\n * bound each operand but not their product, so this bounds the operation itself.\n * Generous enough for any realistic expression; only pathological blow-ups trip\n * it. (Chosen so expressions well beyond normal use still parse; tune if needed.)\n */\nconst MAX_BINARY_OUTCOMES = 100_000_000;\n\n/** Internal bookkeeping attached to a {@link Dice} during parsing. */\nexport interface DicePrivateData {\n /** Marks a DC (saving-throw) check so outcomes are attributed correctly. */\n isDCCheck?: boolean;\n /** The \"other\" distribution recorded by {@link Dice.combine}. */\n except?: Dice | Record;\n /** Keep-highest/lowest selector applied when a die is multiplied out. */\n keep?: (values: number[]) => number;\n}\n\n/**\n * @internal\n */\nexport class Dice {\n private readonly faces: DamageDistribution = {};\n public privateData: DicePrivateData = {};\n // Partial: the object starts empty and gains keys as outcomes are recorded,\n // so the type must not claim every OutcomeType is present. (Previously typed\n // as a full Record via an `as` cast, which lied about missing keys.)\n private outcomeData: Partial> = {};\n private hasHitDistributionCalculated = false;\n public identifier?: string;\n\n constructor(x: number = 0) {\n if (x <= 0) return;\n for (let i = 1; i <= x; i++) {\n this.faces[i] = 1;\n }\n }\n\n getOutcomeDistribution(key: OutcomeType): DamageDistribution | undefined {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n\n const distribution = this.outcomeData[key];\n if (distribution === undefined) return undefined;\n\n return { ...distribution };\n }\n\n getFullOutcomeDistribution(): Partial<\n Record\n > {\n return { ...this.outcomeData };\n }\n\n setOutcomeDistribution(\n key: OutcomeType,\n data: DamageDistribution | undefined\n ): void {\n if (data) {\n this.outcomeData[key] = data;\n } else {\n delete this.outcomeData[key];\n }\n }\n\n hasOutcomeData(key: OutcomeType): boolean {\n if (key === \"hit\") {\n this.ensureHitDistribution();\n }\n const data = this.outcomeData[key];\n return data !== undefined && Object.keys(data).length > 0;\n }\n\n getOutcomeCount(key: OutcomeType, face: number): number {\n return this.outcomeData[key]?.[face] ?? 0;\n }\n\n getAverage(key: OutcomeType): number {\n const distribution = this.getOutcomeDistribution(key);\n if (!distribution) return 0;\n // TODO caching opportunity\n\n const totalCount = Object.values(distribution).reduce(\n (sum, count) => sum + count,\n 0\n );\n const expectedDamage = Object.entries(distribution).reduce(\n (sum, [damage, count]) => sum + Number(damage) * count,\n 0\n );\n if (totalCount === 0) return 0;\n return expectedDamage / totalCount;\n }\n\n // TODO this can be private later if we change how testing works\n calculateHitDistribution(): DamageDistribution {\n const hitValues: DamageDistribution = {};\n\n // Hoist the per-outcome distributions out of the face loop: they are\n // constant across faces, so fetching (and previously cloning) them once is\n // O(faces + outcomes) instead of O(faces × outcomes). The stored maps are\n // only read here, never mutated, so reading them directly is safe and\n // produces identical counts.\n const subtractedOutcomes: (DamageDistribution | undefined)[] = [\n this.outcomeData.crit,\n this.outcomeData.missNone,\n this.outcomeData.missDamage,\n this.outcomeData.saveHalf,\n this.outcomeData.saveFail,\n this.outcomeData.pc,\n ];\n\n for (const [face, totalCount] of Object.entries(this.faces)) {\n const numFace = Number(face);\n let hitCount = totalCount;\n\n for (const distribution of subtractedOutcomes) {\n const outcomeCount = distribution?.[numFace];\n if (outcomeCount) {\n hitCount -= outcomeCount;\n }\n }\n\n // Zero damage should not be counted as hits - they represent misses\n if (numFace === 0) {\n hitCount = 0;\n }\n\n // Defensive clamp: guards against negative hit counts from older\n // inclusion-exclusion logic. Should never trigger in practice.\n if (hitCount < 0) {\n hitCount = 0;\n }\n hitValues[numFace] = hitCount;\n }\n\n return hitValues;\n }\n\n private ensureHitDistribution(): void {\n if (!this.hasHitDistributionCalculated) {\n const hitValues = this.calculateHitDistribution();\n this.setOutcomeDistribution(\"hit\", hitValues);\n this.hasHitDistributionCalculated = true;\n }\n }\n\n // PRIVATE FUNCTIONS\n\n private binaryOp(\n other: Dice | number,\n op: (a: number, b: number) => number,\n diceConstructor?: () => Dice\n ): Dice {\n const result = diceConstructor ? diceConstructor() : new Dice();\n\n const isScalar = typeof other === \"number\";\n const keys1 = this.keys();\n // Hoist the inner die's faces out of the outer loop: they are constant\n // across key1, so computing them once avoids re-allocating the array on\n // every outer iteration.\n const keys2 = isScalar ? [] : (other as Dice).keys();\n\n // Guard the O(faces₁ × faces₂) work before running it: a binary op between\n // two large (but individually legal) dice would otherwise blow up.\n if (!isScalar && keys1.length * keys2.length > MAX_BINARY_OUTCOMES) {\n throw new DiceParseError(\n `Dice operation over ${keys1.length}×${keys2.length} face pairs exceeds the maximum of ${MAX_BINARY_OUTCOMES}`\n );\n }\n\n for (const key1 of keys1) {\n const value1 = this.faces[key1]!;\n\n if (isScalar) {\n const resultKey = op(key1, other as number);\n result.increment(resultKey, value1);\n } else {\n for (const key2 of keys2) {\n const value2 = other.faces[key2]!;\n const resultKey = op(key1, key2);\n result.increment(resultKey, value1 * value2);\n }\n }\n }\n\n return result;\n }\n\n private removeFaces(facesToRemove: number[]): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (!facesToRemove.includes(numKey)) {\n result.faces[numKey] = value;\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // PUBLIC FUNCTIONS\n\n getFaceEntries(): [number, number][] {\n return Object.entries(this.faces).map(([k, v]) => [Number(k), v]);\n }\n\n getFaceMap(): DamageDistribution {\n return { ...this.faces };\n }\n\n get(face: number): number {\n return this.faces[face] ?? 0;\n }\n\n keys(): number[] {\n return Object.keys(this.faces).map(Number);\n }\n\n values(): number[] {\n return Object.values(this.faces);\n }\n\n total(): number {\n return Object.values(this.faces).reduce((sum, value) => sum + value, 0);\n }\n\n public setFace(key: number, value: number): void {\n this.faces[key] = value;\n }\n\n public static scalar(value: number): Dice {\n const result = new Dice();\n result.increment(value, 1);\n return result;\n }\n\n public maxFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.max(...numericKeys);\n }\n\n public minFace(): number {\n const numericKeys = this.keys();\n\n if (numericKeys.length === 0) {\n throw new Error(\"No numeric faces found\");\n }\n\n return Math.min(...numericKeys);\n }\n\n public increment(face: number, count: number): void {\n const current = this.faces[face] || 0;\n this.faces[face] = current + count;\n }\n\n public normalize(scalar: number): Dice {\n const result = new Dice();\n\n for (const [face, count] of Object.entries(this.faces)) {\n result.faces[Number(face)] = count * scalar;\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n // OPERATIONS\n\n public add(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a + b);\n }\n\n public subtract(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a - b);\n }\n\n public conditionalApply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === 0 ? 0 : 1) * b);\n }\n\n public multiply(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a * b);\n }\n\n public addNonZero(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a !== 0 ? a + b : a));\n }\n\n public eq(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a === b ? 1 : 0));\n }\n\n public max(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.max(a, b));\n }\n\n public min(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.min(a, b));\n }\n\n public advantage(): Dice {\n return this.max(this);\n }\n\n public ge(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a >= b ? 0 : 1));\n }\n\n public divide(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => a / b);\n }\n\n public divideRoundUp(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.ceil(a / b));\n }\n\n public divideRoundDown(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => Math.floor(a / b));\n }\n\n public and(other: Dice | number): Dice {\n return this.binaryOp(other, (a, b) => (a && b ? 1 : 0));\n }\n\n private checkTarget(\n other: Dice | number,\n comparisonLogic: (roll: number, target: number) => number\n ): Dice {\n const createResult = () => {\n const result = new Dice();\n result.increment(0, 0); // Success\n result.increment(1, 0); // Failure\n return result;\n };\n\n return this.binaryOp(other, comparisonLogic, createResult);\n }\n\n public dc(other: Dice | number): Dice {\n const dcCheck = (a: number, b: number) => (a >= b ? 0 : 1);\n const result = this.checkTarget(other, dcCheck);\n // Mark this as a DC check (save mechanic) for proper attribute assignment\n result.privateData.isDCCheck = true;\n return result;\n }\n\n public ac(other: Dice | number): Dice {\n const acCheck = (a: number, b: number) => (a >= b ? a : 0);\n return this.checkTarget(other, acCheck);\n }\n\n public deleteFace(face: number): Dice {\n const result = new Dice();\n\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n if (numKey !== face) {\n result.increment(numKey, value);\n }\n }\n\n result.privateData = { ...this.privateData };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public reroll(toReroll: Dice | number): Dice {\n const rerollDice =\n typeof toReroll === \"number\" ? Dice.scalar(toReroll) : toReroll;\n\n const rerollKeys = rerollDice.keys();\n const rerollSet = new Set(rerollKeys);\n const removed = this.removeFaces(rerollKeys);\n let result = new Dice();\n\n for (const face of this.keys()) {\n const wasRerolled = rerollSet.has(face);\n result = result.combine(removed);\n if (wasRerolled) {\n result = result.combine(this);\n }\n }\n\n return result;\n }\n\n // This is not addition and not rolling two dice at once.\n // Instead, it’s mixing two distributions into a single weighted die.\n public combine(other: Dice | number): Dice {\n if (typeof other === \"number\") {\n other = Dice.scalar(other);\n }\n\n // Start by copying \"other\" into a new Dice object\n const result = new Dice();\n for (const [key, value] of Object.entries(other.faces)) {\n result.faces[Number(key)] = value;\n }\n\n // Build the \"except\" dice and add faces from `this` to result\n const except = new Dice();\n for (const [key, value] of Object.entries(this.faces)) {\n const numKey = Number(key);\n result.increment(numKey, value);\n\n // If the key did not already exist in `other`, we remove it from `except`\n if (!(numKey in other.faces)) {\n except.increment(numKey, value); // still tracked in except\n }\n }\n\n result.privateData = { ...this.privateData, except: other };\n result.outcomeData = { ...this.outcomeData };\n return result;\n }\n\n public combineInPlace(other: Dice): void {\n for (const [key, value] of Object.entries(other.faces)) {\n const numKey = Number(key);\n const current = this.faces[numKey] || 0;\n this.faces[numKey] = current + value;\n }\n }\n\n public percent(): DamageDistribution {\n const total = this.total();\n const result: DamageDistribution = {};\n\n for (const [face, count] of Object.entries(this.faces)) {\n result[Number(face)] = count / total;\n }\n\n return result;\n }\n\n public average(): number {\n const total = this.total();\n if (total === 0) return 0;\n\n let sum = 0;\n for (const [key, value] of Object.entries(this.faces)) {\n sum += Number(key) * value;\n }\n\n return sum / total;\n }\n\n /*\n * Convert dice to PMF using OutcomeType labels directly from damage distribution.\n * This is much cleaner than the original complex distribution conversion.\n */\n public toPMF(numEpsilon: number = EPS): PMF {\n const total = this.total();\n if (total === 0) return PMF.empty(numEpsilon);\n\n // Ensure hit distribution is calculated before using it\n this.ensureHitDistribution();\n\n const map = new Map();\n\n // Outcome distributions (counts, not probabilities)\n const hitDistro = this.getOutcomeDistribution(\"hit\") || {};\n const critDistro = this.getOutcomeDistribution(\"crit\") || {};\n const missDistro = this.getOutcomeDistribution(\"missDamage\") || {};\n const saveDistro = this.getOutcomeDistribution(\"saveHalf\") || {};\n const pcDistro = this.getOutcomeDistribution(\"pc\") || {};\n\n // A non-empty save distribution means the \"save\" mechanic was used (e.g.\n // \"save half\"): its success mass is a saveHalf outcome and the remaining\n // (full-damage) mass is a saveFail. The previous heuristic — \"isSaveHalf iff\n // 2×(a half value) appears in the hit distribution\" — false-negatived on odd\n // or constant damage, mislabeling saveHalf as saveFail and saveFail as hit.\n const isSaveHalf = Object.keys(saveDistro).length > 0;\n\n const isDCCheck = this.privateData.isDCCheck === true;\n\n const clampNonNeg = (x: number) => (x < 0 && x > -1e-15 ? 0 : x);\n\n // Process each face value (iterate the internal map directly; this loop\n // only reads it, so the defensive clone from getFaceMap() is unnecessary)\n for (const [faceStr, faceCountRaw] of Object.entries(this.faces)) {\n const face = Number(faceStr);\n const faceCount = Number(faceCountRaw);\n\n // Skip structurally present but empty bins early\n if (faceCount <= 0) continue;\n\n let p = faceCount / total;\n p = clampNonNeg(p);\n\n // Always drop exact zeros to keep PMF semantics clean\n if (!(p > 0)) continue;\n\n // Optional pruning only when epsilon is enabled\n if (numEpsilon >= 0 && p < numEpsilon) continue;\n\n const count: OutcomeLabelMap = {};\n const attr: OutcomeLabelMap = {};\n\n // Add distribution counts and attributions\n if (hitDistro[face]) {\n const c = clampNonNeg(hitDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf || isDCCheck) {\n count.saveFail = c; // full damage on failed save\n attr.saveFail = clampNonNeg((face * hitDistro[face]) / total);\n } else {\n count.hit = c;\n attr.hit = clampNonNeg((face * hitDistro[face]) / total);\n }\n }\n }\n\n if (critDistro[face]) {\n const c = clampNonNeg(critDistro[face] / total);\n if (c > 0) {\n count.crit = c;\n attr.crit = clampNonNeg((face * critDistro[face]) / total);\n }\n }\n\n if (missDistro[face]) {\n const c = clampNonNeg(missDistro[face] / total);\n if (c > 0) {\n count.missDamage = c;\n attr.missDamage = clampNonNeg((face * missDistro[face]) / total);\n }\n }\n\n if (saveDistro[face]) {\n const c = clampNonNeg(saveDistro[face] / total);\n if (c > 0) {\n if (isSaveHalf) {\n count.saveHalf = c; // half damage on successful save\n attr.saveHalf = clampNonNeg((face * saveDistro[face]) / total);\n } else {\n count.saveFail = (count.saveFail ?? 0) + c; // regular fail\n attr.saveFail = clampNonNeg(\n (attr.saveFail ?? 0) + (face * saveDistro[face]) / total\n );\n }\n }\n }\n\n if (pcDistro[face]) {\n const c = clampNonNeg(pcDistro[face] / total);\n if (c > 0) {\n count.pc = c;\n attr.pc = clampNonNeg((face * pcDistro[face]) / total);\n }\n }\n\n // Handle faces with no specific distribution (missNone) for non-save and non-DC checks\n if (!isSaveHalf && !isDCCheck) {\n const distroCountRaw =\n (hitDistro[face] || 0) +\n (critDistro[face] || 0) +\n (missDistro[face] || 0) +\n (saveDistro[face] || 0) +\n (pcDistro[face] || 0);\n\n const unaccountedCount = clampNonNeg(faceCount - distroCountRaw);\n if (unaccountedCount > 0) {\n const frac = clampNonNeg(unaccountedCount / total);\n if (frac > 0) {\n count.missNone = (count.missNone ?? 0) + frac;\n // missNone does 0 damage, so no attr contribution\n }\n }\n }\n\n const bin: Bin = { p, count };\n if (Object.keys(attr).length > 0) {\n bin.attr = attr;\n }\n\n map.set(face, bin);\n }\n\n // Fall back to a sentinel identifier if none was assigned. This indicates\n // a Dice constructed outside the normal parse/build flow.\n const identifier = this.identifier || \"ERROR\";\n\n // Normalize, drop zeros again if any snuck in, then compaction and pruning\n return new PMF(map, numEpsilon, true, identifier).compact(numEpsilon, true);\n }\n}\n\nexport type _DiceInternal = never;\n","import { DiceParseError } from \"../common/errors\";\nimport { LRUCache } from \"../common/lru-cache\";\nimport type { OutcomeType } from \"../common/types\";\nimport type { PMF } from \"../pmf/pmf\";\nimport { Dice } from \"./dice\";\n\ntype DiceOperation = ((this: Dice, other: Dice | number) => Dice) & {\n unary?: boolean;\n};\n\n/**\n * Resource-exhaustion guards. Adversarial expressions (a huge die, a huge dice\n * count, or a keep over a large enumerated pool) can otherwise blow up memory\n * and CPU. These caps are deliberately generous so every legitimate expression\n * the suite exercises (e.g. d100000) still parses.\n */\n// Must stay >= 100000: the suite asserts d100000 (100k faces) parses.\nconst MAX_DIE_SIDES = 1_000_000;\nconst MAX_DICE_COUNT = 10_000;\n// multiplyDiceByDice enumerates faces^count outcomes when a keep is applied.\nconst MAX_KEEP_OUTCOMES = 1_000_000;\n\n/**\n * Internal parse cache for PMFs produced from string expressions.\n * Keyed by cleaned expression (spaces stripped, lowercased) and optional `n` value.\n */\nconst parseCache = new LRUCache(1000);\n\nlet cachingEnabled = true;\n\n/** Enable or disable the internal parse cache. */\nexport function setCachingEnabled(enabled: boolean): void {\n cachingEnabled = enabled;\n if (!enabled) clearParserCache();\n}\n\n/** Returns whether the internal parse cache is currently enabled. */\nexport function getCachingEnabled(): boolean {\n return cachingEnabled;\n}\n\n/** Clears the internal parse cache. */\nexport function clearParserCache(): void {\n parseCache.clear();\n}\n\n/**\n * Parse a dice expression into a PMF.\n *\n * - Expression is case-insensitive and ignores spaces.\n */\nexport function parse(expression: string, n: number = 0): PMF {\n // Check cache first if enabled\n const cleaned = expression.replace(/ /g, \"\").toLowerCase();\n\n if (cachingEnabled) {\n const cacheKey = `${cleaned}:${n}`;\n const cached = parseCache.get(cacheKey);\n if (cached) return cached;\n }\n\n const chars = [...cleaned];\n\n let result: Dice;\n try {\n result = parseExpression(chars, n);\n } catch (error) {\n throw new DiceParseError(\n `Cannot parse dice expression [${expression}]: ${error}`,\n { expression, cause: error }\n );\n }\n\n result.privateData = result.privateData || {};\n result.identifier = cleaned;\n\n if (chars.length > 0) {\n throw new DiceParseError(\n `Unexpected token: '${chars[0]}' from expression: '${expression}'`,\n { expression }\n );\n }\n\n // When creating the PMF, do not epsilon prune\n const resultPMF = result.toPMF(-1);\n if (cachingEnabled) {\n // store using the same cleaned key we used for lookup\n const cacheKey = `${cleaned}:${n}`;\n parseCache.set(cacheKey, resultPMF);\n }\n\n return resultPMF;\n}\n\nfunction combineDiceWithNormalization(\n dice: Dice,\n normValue: number,\n outcomeType: OutcomeType,\n currentNorm: number,\n finalResult: Dice\n): { newNorm: number; updatedResult: Dice } {\n dice = dice.normalize(currentNorm);\n finalResult = finalResult.normalize(normValue);\n finalResult.setOutcomeDistribution(outcomeType, dice.getFaceMap());\n finalResult = finalResult.combine(dice);\n return { newNorm: currentNorm * normValue, updatedResult: finalResult };\n}\n\nfunction parseExpression(arr: string[], n: number): Dice {\n const result = (() => {\n const res = parseArgument(arr, n);\n return typeof res === \"number\" ? Dice.scalar(res) : res;\n })();\n\n let op = parseOperation(arr);\n let finalResult = result;\n\n while (op != null) {\n const arg = !op.unary ? parseArgument(arr, n) : finalResult;\n\n // Handle crit (e.g. xcrit, crit)\n //\n // KNOWN LIMITATION: this peels the maximum FACE of the (already convolved)\n // to-hit distribution to represent the natural 20. That is correct only when\n // the to-hit has no bonus dice. With bonus dice in the to-hit (e.g. Bless,\n // \"d20 + 5 + 1d4\"), a natural 20 is \"d20 == 20 with any bonus\", whose mass is\n // smeared across several total values that also contain non-crit mass, so the\n // d20 identity is lost and the crit slice collapses to 1/(20·∏bonusSides).\n // Use the builder API (d20.plus(...).plus(bonusDie).ac(...).onCrit(...)) for\n // a correct crit probability with bonus to-hit dice. See CHANGELOG.\n let crit: Dice | undefined;\n let critNorm = 1;\n if (arr[0] === \"x\" || arr[0] === \"c\") {\n const isXcrit = arr[0] === \"x\";\n if (isXcrit) assertToken(arr, \"x\");\n assertToken(arr, \"c\");\n assertToken(arr, \"r\");\n assertToken(arr, \"i\");\n assertToken(arr, \"t\");\n\n const count = isXcrit ? parseNumber(arr, n) : 1;\n\n crit = new Dice();\n for (let i = 0; i < count; i++) {\n const max = finalResult.maxFace();\n crit.setFace(max, finalResult.get(max));\n finalResult = finalResult.deleteFace(max);\n }\n\n critNorm = crit.total();\n crit = op.call(crit, parseBinaryArgument(arg, arr, n));\n\n critNorm = crit && critNorm ? crit.total() / critNorm : 1;\n }\n\n // Handle save\n let save: Dice | undefined;\n let saveNorm = 1;\n if (arr[0] === \"s\") {\n assertToken(arr, \"s\");\n assertToken(arr, \"a\");\n assertToken(arr, \"v\");\n assertToken(arr, \"e\");\n\n save = new Dice();\n const min = finalResult.minFace();\n save.increment(min > 0 ? min : 1, finalResult.get(min));\n\n saveNorm = save.total();\n finalResult = finalResult.deleteFace(min);\n save = op.call(save, parseBinaryArgument(arg, arr, n));\n saveNorm = save && saveNorm ? save.total() / saveNorm : 1;\n }\n\n // Handle half damage on hit (potent cantrip)\n let pc: Dice | undefined;\n let pcNorm = 1;\n\n if (arr.length >= 2 && arr[0] === \"p\" && arr[1] === \"c\") {\n assertToken(arr, \"p\");\n assertToken(arr, \"c\");\n\n pc = new Dice();\n const min = finalResult.minFace();\n pc.increment(min > 0 ? min : 1, finalResult.get(min));\n\n const missBefore = pc.total();\n finalResult = finalResult.deleteFace(min);\n\n pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2); // parse the damage\n\n const missAfter = pc ? pc.total() : 0;\n pcNorm = missBefore ? missAfter / missBefore : 1;\n }\n\n // Handle miss\n let miss: Dice | undefined;\n let missNorm = 1;\n\n if (arr[0] === \"m\") {\n assertToken(arr, \"m\");\n assertToken(arr, \"i\");\n assertToken(arr, \"s\");\n assertToken(arr, \"s\");\n\n miss = new Dice();\n const min = finalResult.minFace();\n miss.increment(min > 0 ? min : 1, finalResult.get(min));\n\n missNorm = miss.total();\n finalResult = finalResult.deleteFace(min);\n\n miss = op.call(miss, parseBinaryArgument(arg, arr, n));\n missNorm = miss && missNorm ? miss.total() / missNorm : 1;\n }\n\n let norm = finalResult.total();\n\n finalResult = op.call(finalResult, arg);\n norm = norm ? finalResult.total() / norm : 1;\n\n // Combine dice with normalization\n if (crit) {\n const result = combineDiceWithNormalization(\n crit,\n critNorm,\n \"crit\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (save) {\n const result = combineDiceWithNormalization(\n save,\n saveNorm,\n \"saveHalf\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (miss) {\n const result = combineDiceWithNormalization(\n miss,\n missNorm,\n \"missDamage\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n if (pc) {\n const result = combineDiceWithNormalization(\n pc,\n pcNorm,\n \"pc\",\n norm,\n finalResult\n );\n norm = result.newNorm;\n finalResult = result.updatedResult;\n }\n\n op = parseOperation(arr);\n }\n\n return finalResult;\n}\n\nfunction parseArgument(s: string[], n: number): Dice | number {\n let result = parseArgumentInternal(s, n);\n\n while (true) {\n const next = parseArgumentInternal(s, n);\n if (next === undefined) break;\n\n result = multiplyDiceByDice(result as Dice | number, next);\n }\n\n return result as Dice | number;\n}\n\nfunction multiplyDiceByDice(d1: Dice | number, d2: Dice | number): Dice {\n if (typeof d1 === \"number\") d1 = Dice.scalar(d1);\n if (typeof d2 === \"number\") d2 = Dice.scalar(d2);\n\n const result = new Dice();\n // Keyed by face value. A Map avoids the number→string→parseFloat round-trip of\n // an object and preserves insertion order, which matches d1.keys() ascending\n // order — so the combine order below is identical to the previous version.\n const faces = new Map();\n let normalizationFactor = 1;\n\n for (const key of d1.keys()) {\n let face: Dice;\n\n if (typeof key !== \"number\") {\n continue; // Skip invalid scalar\n }\n\n if (d2.privateData.keep) {\n // Repeat dice2 \"key\" times and apply keep. opDice enumerates the full\n // faces^count outcome space, so guard against a combinatorial blow-up.\n const faceCount = d2.keys().length;\n if (Math.pow(faceCount, key) > MAX_KEEP_OUTCOMES) {\n throw new DiceParseError(\n `Keep enumeration of ${faceCount}^${key} outcomes exceeds the maximum of ${MAX_KEEP_OUTCOMES}`\n );\n }\n const repeat: Dice[] = Array(key).fill(d2);\n face = opDice(repeat, d2.privateData.keep);\n } else {\n face = multiplyDice(key, d2);\n }\n\n normalizationFactor *= face.total();\n faces.set(key, face);\n }\n\n for (const [k, face] of faces) {\n const count = d1.get(k);\n result.combineInPlace(\n face.normalize((count * normalizationFactor) / face.total())\n );\n }\n\n result.privateData.except = {};\n return result;\n}\n\nfunction multiplyDice(n: number, d: Dice): Dice {\n if (n > MAX_DICE_COUNT) {\n throw new DiceParseError(\n `Dice count ${n} exceeds the maximum of ${MAX_DICE_COUNT}`\n );\n }\n if (n === 0) return new Dice(0);\n if (n === 1) return d;\n\n const half = Math.floor(n / 2);\n let result = multiplyDice(half, d);\n result = result.add(result);\n\n if (n % 2 === 1) {\n result = result.add(d);\n }\n\n return result;\n}\n\nfunction opDice(diceList: Dice[], keepFn: (values: number[]) => number): Dice {\n return opDiceInternal(diceList, new Dice(), 0, [], 1, keepFn);\n}\n\nfunction opDiceInternal(\n diceList: Dice[],\n result: Dice,\n index: number,\n values: number[],\n weight: number,\n combineFn: (values: number[]) => number\n): Dice {\n if (index === diceList.length) {\n return result.combine(Dice.scalar(combineFn(values)).normalize(weight));\n }\n\n const currentDice = diceList[index];\n for (const face of currentDice.keys()) {\n values.push(face as number);\n result = opDiceInternal(\n diceList,\n result,\n index + 1,\n values,\n weight * currentDice.get(face),\n combineFn\n );\n values.pop();\n }\n\n return result;\n}\n\nfunction parseArgumentInternal(\n s: string[],\n n: number\n): Dice | number | undefined {\n if (s.length === 0) return;\n\n const c = s[0];\n\n switch (c) {\n case \"(\":\n s.shift();\n return assertToken(s, \")\", parseExpression(s, n));\n\n case \"h\":\n case \"d\":\n return parseDice(s, n);\n\n case \"k\":\n assertToken(s, \"k\");\n return parseKeep(s, n);\n\n case \"n\":\n return parseNumber(s, n);\n\n default:\n if (isDigit(c)) return parseNumber(s, n);\n return;\n }\n}\n\nfunction parseBinaryArgument(\n arg: Dice | number,\n arr: string[],\n n: number\n): Dice {\n if (arr.length >= 4 && arr[0] === \"h\" && peek(arr, \"half\")) {\n assertToken(arr, \"half\");\n\n const diceArg = typeof arg === \"number\" ? Dice.scalar(arg) : arg;\n return diceArg.divideRoundDown(2);\n }\n\n const parsed = parseArgument(arr, n);\n return typeof parsed === \"number\" ? Dice.scalar(parsed) : parsed;\n}\n\nfunction assertToken(s: string[], expected: string, ret?: T): T | undefined {\n for (const ch of expected) {\n const found = s.shift();\n if (found !== ch) {\n throw new Error(`Expected character '${ch}', found '${found}'`);\n }\n }\n return ret;\n}\n\nfunction parseDice(s: string[], n: number): Dice | undefined {\n let rerollOne = false;\n\n if (peek(s, \"hd\") && peekIsNumber(s, 2)) {\n assertToken(s, \"h\");\n assertToken(s, \"d\");\n rerollOne = true;\n } else if (peek(s, \"d\") && peekIsNumber(s, 1)) {\n assertToken(s, \"d\");\n } else {\n return;\n }\n\n const sides = parseNumber(s, n);\n if (sides > MAX_DIE_SIDES) {\n throw new DiceParseError(\n `Die size ${sides} exceeds the maximum of ${MAX_DIE_SIDES}`\n );\n }\n let result = new Dice(sides);\n\n if (rerollOne) {\n // Reroll a rolled 1 exactly once, keeping the second roll (e.g. halfling\n // luck). This is the same semantics as the `reroll 1` operator; the previous\n // deleteFace(1).combine(result) computed a weighted union (P(1)=1/(2s−1)),\n // not a reroll (correct P(1)=1/s²).\n result = result.reroll(1);\n }\n\n return result;\n}\n\nfunction peek(arr: string[], expected: string): boolean {\n if (expected.length > arr.length) return false;\n\n for (let i = 0; i < expected.length; i++) {\n if (arr[i] !== expected.charAt(i)) return false;\n }\n\n return true;\n}\n\nfunction peekIsNumber(arr: string[], index: number): boolean {\n if (index >= arr.length) return false;\n return isDigit(arr[index]) || arr[index] === \"n\";\n}\n\nfunction parseNumber(s: string[], n: number): number {\n let ret = \"\";\n\n while (s.length > 0 && (isDigit(s[0]) || s[0] === \"n\")) {\n const ch = s.shift()!;\n ret += ch === \"n\" ? n.toString() : ch;\n }\n\n if (ret.length === 0) {\n throw new Error(`Expected number, found: '${s[0]}'`);\n }\n\n return parseInt(ret, 10);\n}\n\nfunction isDigit(c: string): boolean {\n return c >= \"0\" && c <= \"9\";\n}\n\nfunction parseKeep(s: string[], n: number): Dice | undefined {\n let keepLowest = false;\n\n if (peek(s, \"l\")) {\n assertToken(s, \"l\");\n keepLowest = true;\n } else if (peek(s, \"h\")) {\n assertToken(s, \"h\");\n keepLowest = false;\n } else {\n return;\n }\n\n const keepCount = parseNumber(s, n);\n const result = parseArgumentInternal(s, n);\n\n if (result instanceof Dice) {\n result.privateData.keep = keepN(keepCount, keepLowest);\n return result;\n }\n\n throw new Error(\"Expected Dice after keep modifier\");\n}\n\nfunction keepN(n: number, low: boolean): (values: number[]) => number {\n return (values: number[]): number => {\n const sorted = [...values].sort((a, b) => (low ? a - b : b - a));\n return sorted.slice(0, n).reduce((sum, val) => sum + val, 0);\n };\n}\n\nfunction parseOperation(s: string[]): DiceOperation | undefined {\n switch (s[0]) {\n case \")\":\n return;\n\n case \"a\":\n assertToken(s, \"ac\");\n return Dice.prototype.ac;\n\n case \"d\":\n assertToken(s, \"dc\");\n return Dice.prototype.dc;\n\n case \"!\":\n assertToken(s, \"!\");\n const adv = Dice.prototype.advantage as DiceOperation;\n adv.unary = true;\n return adv;\n\n case \">\":\n assertToken(s, \">\");\n return Dice.prototype.max;\n\n case \"<\":\n assertToken(s, \"<\");\n return Dice.prototype.min;\n\n case \"+\":\n assertToken(s, \"+\");\n return Dice.prototype.addNonZero;\n\n case \"~\":\n assertToken(s, \"~\");\n assertToken(s, \"+\");\n return Dice.prototype.add;\n\n case \"-\":\n assertToken(s, \"-\");\n return Dice.prototype.subtract;\n\n case \"&\":\n assertToken(s, \"&\");\n return Dice.prototype.combine;\n\n case \"r\":\n assertToken(s, \"reroll\");\n return Dice.prototype.reroll;\n\n case \"*\":\n assertToken(s, \"*\");\n\n if (peek(s, \"*\")) {\n assertToken(s, \"*\");\n return Dice.prototype.multiply;\n }\n\n return Dice.prototype.conditionalApply;\n\n case \"/\":\n assertToken(s, \"/\");\n if (s[0] === \"/\") {\n assertToken(s, \"/\");\n return Dice.prototype.divideRoundDown;\n }\n return Dice.prototype.divideRoundUp;\n\n case \"=\":\n assertToken(s, \"=\");\n return Dice.prototype.eq;\n }\n\n return;\n}\n","import type { Bin } from \"../common/types\";\nimport { EPS } from \"../common/types\";\nimport { PMF } from \"./pmf\";\n\n/** A labeled mixture builder that preserves provenance in Bin.count. */\nexport class Mixture {\n private readonly totals = new Map(); // raw mass per outcome (pre-normalization)\n private readonly labelMass = new Map>(); // raw mass per outcome per label\n private readonly eps: number;\n\n constructor(eps: number = EPS) {\n this.eps = Number.isFinite(eps) ? eps : EPS;\n }\n\n /** Remove all accumulated state. */\n clear(): this {\n this.totals.clear();\n this.labelMass.clear();\n return this;\n }\n\n /** Number of distinct outcome values currently accumulated. */\n size(): number {\n return this.totals.size;\n }\n\n /** Whether a label was ever added. */\n hasLabel(label: L): boolean {\n for (const bag of this.labelMass.values()) if (bag[label]) return true;\n return false;\n }\n\n /**\n * Add a labeled component with a mixture weight.\n * Weight can be any positive finite number. Very small contributions are pruned by eps.\n */\n add(label: L, pmf: PMF, weight = 1): this {\n if (!Number.isFinite(weight) || weight <= 0) return this;\n\n // Stream probabilities from each [value, Bin] pair.\n for (const [v, bin] of pmf) {\n const p = bin.p;\n if (p <= 0) continue;\n\n const add = weight * p;\n if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;\n\n this.totals.set(v, (this.totals.get(v) ?? 0) + add);\n const bag = this.labelMass.get(v) ?? ({} as Record);\n bag[label] = (bag[label] ?? 0) + add;\n this.labelMass.set(v, bag);\n }\n\n return this;\n }\n\n buildPMF(eps: number = EPS): PMF {\n // Kahan sum for robustness.\n let grand = 0;\n let c = 0;\n for (const m of this.totals.values()) {\n const y = m - c;\n const t = grand + y;\n c = t - grand - y;\n grand = t;\n }\n if (!(grand > 0)) throw new Error(\"Mixture: zero total mass\");\n\n const internal = new Map();\n for (const [v, m] of this.totals) {\n if (m <= 0 || Math.abs(m) < this.eps) continue;\n const count = this.labelMass.get(v) ?? {};\n internal.set(v, { p: m / grand, count });\n }\n return new PMF(internal, eps);\n }\n\n /**\n * Produce normalized *per-label* PMFs (labels independent).\n * These are unlabeled PMFs built from the raw mass of that label alone.\n */\n byOutcome(): Record {\n // Collect the set of labels present.\n const labels = new Set();\n for (const bag of this.labelMass.values()) {\n for (const k of Object.keys(bag) as L[]) labels.add(k);\n }\n\n const out = {} as Record;\n for (const label of labels) {\n const m = new Map();\n for (const [v, bag] of this.labelMass) {\n const w = bag[label];\n if (w && Math.abs(w) >= this.eps) m.set(v, w);\n }\n if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);\n }\n return out;\n }\n\n /**\n * Mixture weights per label, normalized to sum to 1 over labels that appeared.\n * Uses raw mass before per-outcome normalization.\n */\n weights(): Record {\n const res = {} as Record;\n for (const [, bag] of this.labelMass) {\n for (const [lab, w] of Object.entries(bag) as [L, number][]) {\n if (!Number.isFinite(w) || w <= 0) continue;\n res[lab] = (res[lab] ?? 0) + w;\n }\n }\n // Normalize\n let total = 0;\n let c = 0;\n for (const v of Object.values(res)) {\n const y = (v as number) - c;\n const t = total + y;\n c = t - total - y;\n total = t;\n }\n if (total > 0) {\n for (const k in res) res[k as L] = res[k as L] / total;\n }\n return res;\n }\n\n toJSON(): {\n totals: Array<[number, number]>;\n labels: Array<[number, Record]>;\n eps: number;\n } {\n return {\n totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),\n labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),\n eps: this.eps,\n };\n }\n\n static mix(\n items: Array<[label: L, pmf: PMF, weight: number]>,\n eps: number = EPS\n ): PMF {\n const mix = new Mixture(eps);\n for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);\n return mix.buildPMF();\n }\n}\n"]} \ No newline at end of file diff --git a/dist/pmf-D5VRghZI.d.cts b/dist/pmf-D5VRghZI.d.cts deleted file mode 100644 index a851fe5..0000000 --- a/dist/pmf-D5VRghZI.d.cts +++ /dev/null @@ -1,1129 +0,0 @@ -/** - * Simple LRU cache implementation - */ -declare class LRUCache { - private readonly maxSize; - private cache; - constructor(maxSize?: number); - get(key: K): V | undefined; - delete(key: K): void; - set(key: K, value: V): this; - clear(): void; - get size(): number; - has(key: K): boolean; - keys(): IterableIterator; - values(): IterableIterator; -} - -/** Mapping from outcome label to probability mass or damage attribution. */ -type OutcomeLabelMap = Partial>; -/** Computational epsilon for pruning negligible probabilities. */ -declare const EPS = 1e-12; -/** A probability bin for a specific damage value. */ -interface Bin { - /** Total probability mass at this damage value. */ - p: number; - /** Per-outcome probability mass contributions at this damage. */ - count: OutcomeLabelMap; - /** Optional per-outcome damage attribution at this damage. */ - attr?: OutcomeLabelMap; -} -interface CritConfig { - critThreshold: number; -} -/** Simple mapping from damage value to probability. */ -type DamageDistribution = Record; -/** Canonical outcome labels supported by the query helpers. */ -type OutcomeType = "crit" | "hit" | "missNone" | "missDamage" | "saveHalf" | "saveFail" | "pc"; -type Rounding = "none" | "floor" | "round" | "ceil"; -/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */ -type RollType = "flat" | "advantage" | "disadvantage" | "elven accuracy"; -/** - * P(critical hit) for the given crit window and d20 {@link RollType}. - * - * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for - * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage - * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps - * the worst of two. - */ -declare function critProbability(critRange: number, rollType?: RollType): number; -/** - * The canonical "clean miss" outcome — a point of zero damage with no rider. - * This is the {@link OutcomeType} that attribution charts and outcome stats key - * on, and is distinct from the builder's attack-resolution `miss` weight label. - */ -declare const MISS_NONE_OUTCOME: OutcomeType; -/** - * All outcome types in canonical severity order — clean miss → crit. This is - * also the natural stacking order for attribution charts (least- to - * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly - * once; use it instead of hand-maintained per-consumer outcome tables. - */ -declare const ALL_OUTCOME_TYPES: OutcomeType[]; -/** - * Outcome types in display order for stats / breakdown rows — most prominent - * first (crit, hit, …) down to the clean miss. - */ -declare const OUTCOME_DISPLAY_ORDER: OutcomeType[]; -/** - * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}). - * Labels not present in `order` sort after known ones, alphabetically — so - * ad-hoc/test labels outside the {@link OutcomeType} union stay stable. - */ -declare function sortOutcomes(outcomes: Iterable, order?: readonly string[]): T[]; -declare const onAnyHit: OutcomeType[]; -declare const onCritOnly: OutcomeType[]; -declare const onHitOnly: OutcomeType[]; -declare const onMissOnly: OutcomeType[]; -declare const onMissDamageOnly: OutcomeType[]; -declare const onSaveHalfOnly: OutcomeType[]; -declare const onSaveFailOnly: OutcomeType[]; -declare const onPotentCantripOnly: OutcomeType[]; - -/** - * Query interface for analyzing dice roll probability distributions. - * - * Combines multiple attack PMFs and provides statistical analysis methods for: - * - Basic statistics (mean, variance, min/max, percentiles) - * - Probability queries (hit chances, success rates, exact counts) - * - Damage analysis (ranges by outcome type, expected values) - * - Data export (charts, tables, visualizations) - * - */ -declare class DiceQuery { - readonly singles: PMF[]; - private readonly _eps; - private readonly _combinedProvided; - private _combined?; - private _combinedWithAttr?; - constructor(singles: PMF | PMF[], combined?: PMF, eps?: number); - /** - * The combined damage distribution of all single PMFs (their convolution), - * normalized to total probability 1. - * - * Computed lazily on first access and cached. Queries that only need - * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, - * {@link DiceQuery.stddev} — never trigger this convolution. - */ - get combined(): PMF; - private static readonly DEFAULT_OUTCOMES; - /** - * Returns a new PMF with damage attribution metadata populated. - * - * This method computes attribution on-demand for builder-generated PMFs, - * enabling them to work with damage attribution charts. The `attr` field - * tracks how much damage each outcome type contributes at each damage value. - * - * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) - * - * Performance: Cached after first call. Adds minimal overhead vs `combined`. - * - * @returns PMF with attr field populated for damage attribution charts - * - * @example - * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) - * const query = attack.toQuery() - * const pmf = query.combinedWithAttribution() - * // Now pmf can be used with toDamageAttributionChartSeries() - */ - combinedWithAttribution(): PMF; - /** - * Per-label `damage value → probability mass` series for the combined, - * attribution-carrying distribution — the provenance core of the stacked - * damage-attribution chart. Convenience for - * `combinedWithAttribution().attributionByValue()`; see - * {@link PMF.attributionByValue}. - */ - attributionByValue(): Map>; - /** - * How many of the independent single PMFs can produce the given outcome - * label. Useful for "all of them succeeded" style probabilities where the - * exponent is the number of contributing attacks (see - * {@link DiceQuery.probExactlyK}). - */ - countSinglesWith(label: string): number; - /** - * Returns the expected damage across all possible outcomes. - * - * Example: `query.mean()` → 12.5 - * Use case: "What's my average damage per round?" - */ - mean(): number; - /** - * Returns the variance of the damage distribution. - * - * Example: `query.variance()` → 45.2 - * Use case: "How much does my damage vary from the average?" - * High variance means higher risk/reward. Lower variance means more consistent damage. - */ - variance(): number; - /** - * Returns the standard deviation of the damage distribution. - * - * Example: `query.stdev()` → 6.7 - * Use case: "What's the typical spread around my average damage?" - * Used to determine how consistent the damage is. - */ - stddev(): number; - /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ - stdev(): number; - /** - * Returns the Cumulative Distribution Function. - */ - cdf(x: number): number; - /** - * Returns the probability of dealing X damage or less. - * In statistics, this is called the cumulative distribution function (CDF). - * Example: `query.cdf(20)` → 0.75 - * Use case: "What's the chance I deal 20 damage or less?" - */ - probTotalAtMost(x: number): number; - /** - * Returns the Complementary Cumulative Distribution Function. - */ - ccdf(x: number): number; - /** - * Returns the probability of dealing at least X damage. - * - * Example: `query.probTotalAtLeast(25)` → 0.35 - * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" - */ - probTotalAtLeast(threshold: number): number; - /** - * Returns damage values at specific percentiles. - * - * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] - * Use case: "What are my 25th, 50th, and 75th percentile damage values?" - */ - percentiles(percentileValues: number[]): number[]; - /** - * Returns the minimum possible damage. - * - * Example: `query.min()` → 0 - * Use case: "What's the worst-case damage if everything misses?" - */ - min(): number; - /** - * Returns the maximum possible damage. - * - * Example: `query.max()` → 56 - * Use case: "What's the best-case damage if everything crits and rolls max?" - */ - max(): number; - private singleProb; - /** - * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it - * carries ANY of `labels`", over the n independent singles. - * - * Each single's per-event success probability is the Poisson-binomial - * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), - * computed exactly once. The binomial DP then runs once to produce the whole - * distribution, so the array-label paths of probExactlyK / probAtLeastK / - * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and - * re-running the DP per requested k. - */ - private countDistribution; - probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number; - /** - * Returns the probability that at least one attack has the specified outcome(s). - * - This is the complement of probAtMostK(labels, 0) - * - * Examples: - * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) - * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) - * - * Use cases: - * - "What's the chance at least one of my attacks connects?" - * - * Note: - * - * - You have to pass in an array of labels to avoid double-counting if you are - * using multiple labels. You cannot just add them. - */ - probAtLeastOne(labels: OutcomeType | OutcomeType[]): number; - /** - * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. - * - * Uses dynamic programming to efficiently calculate the probability distribution - * of how many attacks will have the specified outcome, accounting for different - * success probabilities across individual attacks. - * - * Example: For 3 attacks with 50% hit chance each, returns: - * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] - * - * @param label - The outcome type to count - * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) - * @returns Array where index K contains P(exactly K attacks have the label) - */ - private computeBinomialProbabilities; - /** - * Returns the probability that exactly K attacks result in the specified outcome(s). - * - * Single label examples: - * - probExactlyK('hit', 2) = probability exactly 2 attacks hit - * - probExactlyK('crit', 1) = probability exactly 1 attack crits - * - probExactlyK('crit', 0) = probability no attacks crit - * - * Array examples: - * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed - * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds - * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss - * - * Use cases: - * - "What's the chance exactly one of my attacks hits?" - * - "How likely am I to get exactly 2 successes out of 3 attacks?" - * - "What's the probability that exactly half my attacks succeed?" - * - * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. - * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. - */ - probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number; - /** - * Returns the probability that AT MOST K attacks result in the specified outcome(s). - * - * Single label examples: - * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) - * - probAtMostK('crit', 0) = probability no attacks crit - * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss - * - * Array examples: - * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds - * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) - * - * Use cases: - * - "What's the chance that at most one attack hits?" (rest miss) - * - "How likely am I to have mostly failures?" (at most 1 success) - * - "What's the probability of a really bad turn?" (at most 0 successes) - * - */ - probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number; - /** - * Returns the expected damage attributed to specific outcome types. - * - * Single label examples: - * - expectedDamageFrom('hit') = expected damage from hit components - * - expectedDamageFrom('crit') = expected damage from crit components - * - * Array examples: - * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success - * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses - * - * Use cases: - * - "How much damage do I expect from successful attacks?" - * - "What's the damage contribution from critical hits specifically?" - * - "How much damage comes from miss effects (like save-for-half spells)?" - */ - expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number; - /** - * Returns damage statistics for scenarios where AT LEAST ONE attack results in - * the specified outcome(s). - * - * This method answers "What happens when things go reasonably well?" rather than - * "What's the theoretical maximum?" It includes mixed scenarios which are more - * common and tactically relevant than pure scenarios. - * - * Single label examples: - * - damageStatsFrom('hit') = damage range when at least one attack hits - * - damageStatsFrom('crit') = damage range when at least one attack crits - * - * Array examples: - * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds - * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses - * - * Tactical Use Cases: - * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" - * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" - * - "Should I use this risky spell if it has good damage when it works?" - * - "What's my damage potential when something goes right?" (vs pure failure) - * - * Combat Planning Examples: - * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" - * (Much more useful than "You average 50 damage including complete misses") - * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" - * - Resource management: "If I hit anything, I'll likely finish this enemy" - * - * Statistical Note: - * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which - * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. - * - * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an - * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and - * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than - * E[dmg | the label occurs]. For a single attack both are the plain - * conditional figures. Use {@link probAtLeastOne} for the scenario probability. - * - * @example - * // High-level tactical planning - * const successStats = query.damageStatsFrom('hit') - * const successChance = query.probAtLeastOne('hit') - * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) - */ - damageStatsFrom(labels: OutcomeType | OutcomeType[]): { - min: number; - max: number; - avg: number; - count: number; - }; - /** - * Returns damage statistics for scenarios where ALL attacks result in the specified - * outcome, calculated by leveraging the pure partition of singles. - * - * This method answers "What's the theoretical best/worst case?" and "What are the - * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. - * - * Examples: - * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) - * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) - * - * UI and Display Use Cases: - * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) - * - "Best case scenario" vs "worst case scenario" analysis - * - Mathematical verification: "Does our hit damage calculation match manual math?" - * - Clean damage type attribution: "How much comes from base hits vs crits?" - * - * Design and Balance Use Cases: - * - Game designers: "What's the damage ceiling if someone gets lucky?" - * - Character optimization: "What's my absolute maximum potential?" - * - Ability comparison: "Which build has higher crit ceiling?" - * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" - * - * Mathematical Use Cases: - * - Validating complex calculations against simple manual math - * - Understanding damage component contributions in isolation - * - Separating luck (crit variance) from consistency (hit variance) - * - Building intuition about damage sources - * - * When to Use This vs damageStatsFrom(): - * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons - * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios - * - * Statistical Note: - * Pure scenarios (all hits, all crits) are rare but represent clear mathematical - * boundaries. These stats help understand the "shape" of your damage potential. - * - * @example - * // UI display logic - * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" - * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" - * - * // vs tactical planning (use damageStatsFrom instead) - * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios - */ - combinedDamageStats(targetLabel: OutcomeType): { - min: number; - max: number; - avg: number; - count: number; - }; - /** - * Returns the probability that at least one attack carries ANY of the - * specified labels (the marginal P(≥1) across the independent attacks). - * - * Examples: - * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) - * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) - * - * Use cases: - * - "What's the chance my resolution includes a success label?" - * - "How likely am I to get any hits or crits across all attacks?" - * - * Note: this must NOT be computed by summing `combined` bin probabilities. A - * single combined damage total is reachable by many outcome combinations and - * a bin can hold several labels at once, so summing `bin.p` over bins that - * contain a label over-counts. The correct marginal is the Poisson-binomial - * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. - */ - probabilityOf(labels: OutcomeType | OutcomeType[]): number; - /** - * Returns the probability of missing (any type of miss). - * - * Example: `query.missChance()` → 0.04 - * Use case: "What's the chance I miss completely this turn?" - */ - missChance(): number; - /** - * Returns data formatted for plotting damage probability distribution. - * - * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] - * Use case: "I want to visualize my damage distribution in a chart." - */ - toChartSeries(): Array<{ - x: number; - y: number; - }>; - /** - * Returns tabular data showing damage values and their probability breakdowns. - * - * Example: `query.toLabeledTable(['hit', 'crit'])` → - * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] - * - * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." - */ - toLabeledTable(labels?: OutcomeType[]): Array<{ - damage: number; - total: number; - } & Record>; - /** - * Returns data for stacked charts with unconditional per-label probability mass per damage. - * - * - Each dataset value equals the unconditional probability mass for that label at that damage - * (i.e., `bin.count[label]`). - * - Column sums may be less than the total probability `bin.p` when you omit labels or when - * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. - * - This behavior matches tests that expect raw per-label mass (not proportional scaling). - * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. - * - * @example - * query.toStackedChartData(['hit', 'crit']) - * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} - */ - toStackedChartData(labels?: OutcomeType[], epsilon?: number): { - labels: number[]; - datasets: Array<{ - label: string; - data: number[]; - }>; - }; - /** - * Returns pure mathematical data for attribution charts showing outcome contributions. - * - * Automatically discovers all outcome types present in the PMF, applies filtering rules, - * and returns proportional data suitable for stacked visualization. - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and proportional data - * - * @example - * query.toAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} - */ - toAttributionChartSeries(options?: { - stackOrder?: string[]; - filterRules?: (outcome: string, damage: number) => boolean; - asPercentages?: boolean; - }): { - support: number[]; - outcomes: string[]; - data: { - [outcome: string]: number[]; - }; - }; - /** - * Returns pure mathematical data for damage attribution charts showing damage contribution - * from each outcome type at each damage value. - * - * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of - * bin.count (probability attribution). - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) - * @returns Pure data structure with support, outcomes, and damage attribution data - * - * @example - * query.toDamageAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} - */ - toDamageAttributionChartSeries(options?: { - stackOrder?: string[]; - filterRules?: (outcome: string, damage: number) => boolean; - asPercentages?: boolean; - }): { - support: number[]; - outcomes: string[]; - data: { - [outcome: string]: number[]; - }; - }; - /** - * Returns pure mathematical data for outcome attribution charts showing which - * attack outcome combinations can produce each damage value. - * - * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks - * outcome combinations - answering "what attack outcomes produced this damage?" - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and outcome combination probabilities - * - * @example - * query.toOutcomeAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} - */ - toOutcomeAttributionChartSeries(options?: { - stackOrder?: string[]; - filterRules?: (outcome: string, damage: number) => boolean; - asPercentages?: boolean; - }): { - support: number[]; - outcomes: string[]; - data: { - [outcome: string]: number[]; - }; - }; - /** - * Returns pure mathematical data for cumulative distribution function (CDF). - * Shows P(X ≤ x) - the probability of getting at most x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and cumulative probabilities - * - * @example - * query.toCDFSeries() - * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} - */ - toCDFSeries(asPercentages?: boolean): { - support: number[]; - data: number[]; - }; - /** - * Returns pure mathematical data for complementary cumulative distribution function (CCDF). - * Shows P(X ≥ x) - the probability of getting at least x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and complementary cumulative probabilities - * - * @example - * query.toCCDFSeries() - * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} - */ - toCCDFSeries(asPercentages?: boolean): { - support: number[]; - data: number[]; - }; - /** Probability of doing strictly more than threshold damage (default >0). */ - probDamageGreaterThan(threshold?: number): number; - /** All outcome keys actually present (typed & ordered if you pass an order). */ - outcomeKeys(order?: OutcomeType[]): OutcomeType[]; - /** Total probability per outcome across the PMF. */ - outcomeTotals(outcomes?: OutcomeType[]): Map; - /** Conditional damage range per outcome (min/avg/max of X | outcome). */ - outcomeDamageRanges(outcomes?: OutcomeType[]): Map; - /** - * Snapshot of the distribution in the exact shape the UI consumes. - * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) - * - damageRange is conditional on the outcome occurring - * - * The outcome probabilities use the correct Poisson-binomial marginals - * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), - * so they are always valid probabilities in [0,1]. - * - * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from - * the combined PMF's `count`, which the convolution accumulates as an EXPECTED - * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] - * rather than a clean conditional expectation. It is correct for a single - * attack. - */ - snapshot(order?: readonly OutcomeType[]): Snapshot; - /** - * PMF Transformation Methods - * - * These methods provide a fluent API for transforming dice queries by wrapping - * the underlying PMF transformation methods. All operations work on the combined - * PMF and return new DiceQuery instances. - */ - /** - * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). - * - * @returns New DiceQuery with normalized combined PMF - */ - normalize(): DiceQuery; - /** - * Returns a new DiceQuery with low-probability outcomes removed. - * - * @param eps Minimum probability threshold (defaults to PMF epsilon) - * @param keepFinalBin Whether to keep the highest damage bin regardless of probability - * @returns New DiceQuery with compacted combined PMF - */ - compact(eps?: number, keepFinalBin?: boolean): DiceQuery; - /** - * Returns a new DiceQuery with an additional scaled branch added. - * Useful for conditional outcomes like "30% chance of opportunity attack". - * - * @param branch DiceQuery to add as a scaled branch - * @param probability Probability of the branch occurring (0-1) - * @returns New DiceQuery combining this query with the scaled branch - * - * @example - * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); - * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); - */ - addScaled(branch: DiceQuery, probability: number): DiceQuery; - /** - * Returns a new DiceQuery with all probabilities scaled by a factor. - * Used for conditional scenarios where the entire outcome has reduced probability. - * - * @param factor Scaling factor for probabilities - * @returns New DiceQuery with scaled probabilities - * - * @example - * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario - */ - scaleMass(factor: number): DiceQuery; - totalMass(): number; - /** - * Returns a new DiceQuery with damage values transformed by a function. - * Useful for applying modifiers, resistances, or other damage transformations. - * - * @param damageTransformFunction Function to transform each damage value - * @returns New DiceQuery with transformed damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage - * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage - */ - mapDamage(damageTransformFunction: (damageValue: number) => number): DiceQuery; - /** - * Returns a new DiceQuery with damage values scaled by a factor. - * Convenient wrapper around mapDamage for multiplicative scaling. - * - * @param factor Scaling factor for damage values - * @param rounding Rounding method: "floor" (default), "round", or "ceil" - * @returns New DiceQuery with scaled damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const doubled = baseAttack.scaleDamage(2); // Double damage - * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded - */ - scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): DiceQuery; - /** - * Returns a new DiceQuery combining this query with another via convolution. - * Equivalent to rolling both queries independently and adding results. - * It is important to use this rather than combing()ing the PMFs directly! - * This method maintains the provenance of the PMFs which is needed for damage attribution. - * Combining the .combined PMFs directly is still valid for DPR calculations but - * is not statistically sound for queries. - * - * @param other DiceQuery to combine with - * @param eps Optional epsilon for precision control - * @returns New DiceQuery representing the combined outcome - * - * @example - * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); - * const bothAttacks = mainAttack.convolve(bonusAttack); - */ - convolve(other: DiceQuery): DiceQuery; - /** - * First-success split over an ordered list of DISTINCT single-swing PMFs. - * Each PMF may have different success/subset probabilities (from labels). - * - * successOutcome: e.g., ["success"] or ["hit", "crit"] - * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success - * - * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] - */ - firstSuccessSplit(successOutcome: OutcomeType | OutcomeType[], subsetOutcome: OutcomeType | OutcomeType[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number]; -} -type OutcomeSnapshot = { - atLeastOneProbability: number; - allProbability: number; - damageRange: { - min: number; - avg: number; - max: number; - }; -}; -type Snapshot = { - averageDPR: number; - damageChance: number; - percentiles: { - p25: number; - p50: number; - p75: number; - }; - outcomes: Map; -}; - -declare const pmfCache: LRUCache; -/** - * Probability Mass Function for discrete damage distributions. - */ -declare class PMF { - readonly map: Map; - readonly epsilon: number; - readonly normalized: boolean; - readonly identifier: string; - private _preservedProvenance; - private static __anonIdCounter; - private _support?; - private _min?; - private _max?; - private _totalMass?; - private _mean?; - private _variance?; - private _stdev?; - private _fingerprint?; - constructor(map?: Map, epsilon?: number, normalized?: boolean, identifier?: string, _preservedProvenance?: boolean); - static empty(epsilon?: number, identifier?: string): PMF; - static zero(epsilon?: number): PMF; - static delta(value: number, epsilon?: number): PMF; - /** - * Point mass at damage 0 tagged with the canonical `missNone` outcome. - * - * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the - * builder's attack-resolution vocabulary. This uses the `missNone` - * {@link OutcomeType} that the attribution charts and outcome stats key on, - * so it is the correct "clean miss / no damage" delta for provenance-aware - * mixtures feeding those consumers. - */ - static missNone(epsilon?: number): PMF; - static emptyMass(): PMF; - [Symbol.iterator](): IterableIterator<[number, Bin]>; - static clearCache(): void; - /** - * Creates a conditional PMF from two branches (success and failure) and a probability. - * This is the core logic for modeling any probabilistic event where there are two - * distinct outcomes. - */ - static branch(successPMF: PMF, failurePMF: PMF, successProbability: number): PMF; - /** - * withProbability() - * - * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). - * - * Think of this as a shortcut for: - * pmf.gate(p, PMF.zero()) - * - * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, - * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. - * - * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. - * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. - * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) - * use query.firstSuccessSplit() to get the exact probabilities. - * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). - * - */ - static withProbability(successPMF: PMF, probability: number): PMF; - /** - * gate() - * - * A conditional wrapper around branch() that applies this PMF with probability `p`, - * and applies a provided fallback PMF otherwise. - * - * This is useful for modeling a binary choice between two outcomes: - * - The "success" outcome (this PMF) happens with probability `p`. - * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. - * - * Examples: - * - 25% chance to include an opportunity attack, otherwise nothing: - * attackPMF.gate(0.25, PMF.zero()) - * - * - 50% chance to deal fireball damage, otherwise cone of cold damage: - * fireballPMF.gate(0.5, coneOfColdPMF) - * - * Relationship to other helpers: - * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. - * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). - * - * @param p Probability of applying this PMF (between 0 and 1). - * @param fallback PMF to apply when this PMF is *not* selected. - * @returns A new PMF representing the weighted mixture of this PMF and the fallback. - */ - gate(p: number, fallback: PMF): PMF; - /** - * PMF.exclusive() - * - * Builds a single PMF from a set of mutually exclusive weighted outcomes. - * Exactly one of the provided options will occur. - * - * Each option has: - * - A PMF representing its outcome (e.g., damage dice). - * - A weight representing its probability of being selected. - * - * Notes: - * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for floating point rounding. - */ - static exclusive(options: Array<{ - pmf: PMF; - weight: number; - } | [PMF, number]>, eps?: number): PMF; - /** - * PMF.mix() - * - * Builds a PMF as a linear combination of input PMFs with the given weights. - * Unlike `exclusive`, this does NOT: - * - enforce that weights sum to 1 - * - add leftover probability to δ0 (PMF.zero()) - * - * Use when outcomes are not mutually exclusive, or for interpolation/blending. - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for skipping tiny weights. - */ - static mix(options: Array<{ - pmf: PMF; - weight: number; - } | [PMF, number]>, eps?: number): PMF; - /** - * Adds damage attribution metadata to this PMF based on existing count metadata. - * For each bin, sets attr[outcome] = damage × count[outcome]. - * - * This enables damage attribution charts to work with builder-generated PMFs. - * The parser generates attr automatically, but builder PMFs only have count. - * - * @returns New PMF with attr field populated in each bin - */ - /** - * Returns true if this PMF already carries damage attribution metadata. - * - * Only the first positive-damage bin is inspected (parser-generated PMFs - * populate `attr` uniformly), so this is O(1) in practice. - */ - hasAttribution(): boolean; - withAttribution(): PMF; - /** - * General-purpose N-way mixture. - * weights: Array of [weight, PMF]. - * - * Example: PMF.mixN([ - * [pMiss, zero], - * [pHit, hitPMF], - * [pCrit, critPMF], - * ]); - */ - static mixN(weights: [number, PMF][], eps?: number): PMF; - private setPreservedProvenance; - preservedProvenance(): boolean; - private getPowerCacheKey; - /** - * Efficiently computes this PMF convolved with itself `n` times. - * Uses exponentiation by squaring to reduce total convolutions. - * n must be a positive integer. - * * - * * NOTE: This folds multiple independent attacks into a single PMF. - * As a result, The power() method causes a loss of data provenance. - * This is ONLY SAFE if you are trying to calculate masses. - * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). - */ - power(n: number, eps?: number): PMF; - replicate(n: number): PMF[]; - mass(): number; - outcomeMass(outcome: string): number; - faceTotal(): number; - normalize(): PMF; - /** - * Returns a copy with negligible probabilities removed (p < eps). - * If keepFinalBin is true, the bin with the largest key is always kept, - * even if its probability is below eps. count/attr submaps are still cleaned. - */ - compact(eps?: number, keepFinalBin?: boolean): PMF; - support(): number[]; - min(): number; - max(): number; - /** - * Returns the expected (mean) damage value. - * Cached for performance since this requires iterating through all bins. - */ - mean(): number; - /** - * Returns the variance of the damage distribution. - * Cached for performance since this requires mean calculation plus iteration. - */ - variance(): number; - /** - * Returns the standard deviation of the damage distribution. - */ - stdev(): number; - /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ - private static cloneBin; - /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ - private static scaleBin; - private static mergeInto; - add(other: PMF): PMF; - /** - * Returns a new PMF with a scaled branch added to this one. - * The branch PMF is scaled by the given probability before merging - * This will be very useful for conditional effects and for being - * able to model "I can probably have this opportunity attack 40% of rounds" - * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes - */ - addScaled(branch: PMF, probability: number): PMF; - /** - * Redistributes probability mass to model an effect that only occurs with - * probability `frequency` — a conditional attack, an on-hit rider, or a - * sub-one AoE target fraction. - * - * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, - * per-label `count`, AND per-label `attr` — and the freed mass is moved into - * the miss bin at damage 0, tagged with the canonical `missNone` outcome. - * Total probability mass is preserved. - * - * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage - * attribution (`attr`) intact, so a frequency-scaled PMF still renders - * correctly in the damage-attribution charts. - * - * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` - * collapses all mass into the miss bin. The miss outcome is assumed to be - * encoded at damage value 0. - * - * @param frequency Probability in [0, 1] that the effect occurs. - */ - applyHitFrequency(frequency: number): PMF; - scaleMass(factor: number): PMF; - mapDamage(damageTransformFunction: (damageValue: number) => number): PMF; - scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): PMF; - private getPMFCombineCacheKey; - /** - * A small content fingerprint (mass + bin count + face sum) so convolution - * cache keys change if the underlying numbers do. Memoized because a PMF is - * immutable once constructed — this avoids re-summing every key on each - * convolve() call (including cache hits). - */ - fingerprint(): string; - convolve(other: PMF, eps?: number, raw?: boolean): PMF; - combineRaw(other: PMF, eps?: number): PMF; - private static reduceConvolveLeft; - /** - * Convolves multiple PMFs using linear convolution with automatic caching. - * Uses a left-to-right accumulation approach for maximum cache reuse. - * Each convolve() call automatically uses the convolution cache for performance. - * - * This linear approach provides better cache hits than pairwise because: - * - Intermediate results are more predictable and stable - * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) - * - Order-independent cache keys work better with consistent build patterns - */ - static convolveMany(pmfList: PMF[], eps?: number): PMF; - /** - * Returns a plain, JSON-serializable representation of this PMF. - * - * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces - * the expected output (no double-encoding). Use {@link PMF.fromJSON} to - * reconstruct, or {@link PMF.toJSONString} if you need the string directly. - */ - toJSON(): { - bins: Array<[number, Bin]>; - normalized: boolean; - identifier: string; - }; - /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ - toJSONString(): string; - static fromJSON(jsonData: { - bins: Array<[number, Bin]>; - normalized?: boolean; - identifier?: string; - }): PMF; - /** - * Relative pruning with optional top-K floor. - * Keeps bins with p >= epsRel * peak, always keeps min and max damage, - * optionally guarantees at least `minBins` survivors by adding top-K. - * Returns a new, non-normalized PMF. - */ - prune(epsRel: number, minBins?: number): PMF; - /** Probability mass at exactly x. */ - pAt(x: number): number; - /** - * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. - * Assumes a miss is encoded as the damage-0 bin (the convention used across - * attack/save PMFs). The dual of {@link missProbability}. - */ - hitProbability(): number; - /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ - missProbability(): number; - /** - * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width - * damage buckets, aggregating probability mass (and `count`/`attr` - * provenance) into each bucket's start value. Returns this PMF unchanged when - * its integer support already fits within `maxBuckets`. - * - * This is a lossy display/downsampling transform (bucket start replaces the - * exact damage value) — use it for charting wide distributions, not for DPR - * math. - */ - rebin(maxBuckets: number): PMF; - /** Dense integer support from min..max (inclusive). - * Useful for showing empty bars in charts. - */ - denseSupport(): number[]; - /** CDF at x: P(X ≤ x). */ - cdfAt(x: number): number; - /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ - quantile(p: number): number; - /** Get outcome probability at specific damage value. */ - outcomeAt(damage: number, outcome: string): number; - /** Get all outcome types present in this PMF. */ - outcomes(): string[]; - /** Get total probability of an outcome across all damage values. */ - outcomeProbability(outcome: string): number; - /** Get damage attribution for an outcome at specific damage value. */ - outcomeAttributionAt(damage: number, outcome: string): number; - /** Get all outcome data at specific damage value. */ - binAt(damage: number): { - p: number; - count: Record; - attr?: Record; - } | null; - /** Check if outcome exists in this PMF. */ - hasOutcome(outcome: string): boolean; - /** - * Split each damage value's probability mass across outcome labels, returning - * per-label maps of `damage value → probability mass attributable to that - * label`. Summing over labels at a given value recovers that value's `p`. - * - * Damage-bearing bins are split by `attr` weight (the share of damage each - * outcome contributed); the clean-miss bin at 0 is split by `count` weight - * (there is no damage to attribute). Attribution is computed on demand via - * {@link withAttribution} when absent, so builder-generated PMFs work too. - * - * This is the provenance core of the stacked damage-attribution chart — the - * caller only maps these series into its rendering format (colors, binning, - * axis labels). - */ - attributionByValue(): Map>; - tailProbGE(t: number): number; - tailProbGT(t: number): number; - /** - * Returns a new PMF containing only bins where the specified outcome has non-zero probability. - * This creates a marginal distribution for the given outcome type, with probabilities - * scaled to represent the unconditional mass attributable to that outcome. - */ - filterOutcome(outcome: string): PMF; - /** - * Calculates probabilities for first-success outcomes across n independent attempts. - * - * @param pSuccess - Total probability of any success on a single attempt. - * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). - * @param n - Number of independent attempts. - * - * Returns: - * - pSpecificSuccess: Probability that the first success was of the "special" type - * - pGeneralSuccess: Probability that the first success was of the non-special type - * - pNone: Probability that no successes occurred - * - pAny: Probability that at least one success occurred - */ - static firstSuccessWeights(pSuccess: number, pSpecial: number, n: number): { - pSpecificSuccess: number; - pGeneralSuccess: number; - pNone: number; - pAny: number; - }; - mapValues(f: (v: number) => number, eps?: number, opts?: { - rounding?: Rounding; - preserveCounts?: boolean; - }): PMF; - static fromMap(m: Map, eps?: number, { requireIntegerValues }?: { - requireIntegerValues?: boolean; - }): PMF; - query(): DiceQuery; -} - -export { ALL_OUTCOME_TYPES as A, type Bin as B, type CritConfig as C, type DamageDistribution as D, EPS as E, LRUCache as L, MISS_NONE_OUTCOME as M, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type OutcomeType as a, type RollType as b, critProbability as c, OUTCOME_DISPLAY_ORDER as d, onCritOnly as e, onHitOnly as f, onMissOnly as g, onMissDamageOnly as h, onSaveHalfOnly as i, onSaveFailOnly as j, onPotentCantripOnly as k, DiceQuery as l, type OutcomeSnapshot as m, onAnyHit as o, pmfCache as p, sortOutcomes as s }; diff --git a/dist/pmf-D5VRghZI.d.ts b/dist/pmf-D5VRghZI.d.ts deleted file mode 100644 index a851fe5..0000000 --- a/dist/pmf-D5VRghZI.d.ts +++ /dev/null @@ -1,1129 +0,0 @@ -/** - * Simple LRU cache implementation - */ -declare class LRUCache { - private readonly maxSize; - private cache; - constructor(maxSize?: number); - get(key: K): V | undefined; - delete(key: K): void; - set(key: K, value: V): this; - clear(): void; - get size(): number; - has(key: K): boolean; - keys(): IterableIterator; - values(): IterableIterator; -} - -/** Mapping from outcome label to probability mass or damage attribution. */ -type OutcomeLabelMap = Partial>; -/** Computational epsilon for pruning negligible probabilities. */ -declare const EPS = 1e-12; -/** A probability bin for a specific damage value. */ -interface Bin { - /** Total probability mass at this damage value. */ - p: number; - /** Per-outcome probability mass contributions at this damage. */ - count: OutcomeLabelMap; - /** Optional per-outcome damage attribution at this damage. */ - attr?: OutcomeLabelMap; -} -interface CritConfig { - critThreshold: number; -} -/** Simple mapping from damage value to probability. */ -type DamageDistribution = Record; -/** Canonical outcome labels supported by the query helpers. */ -type OutcomeType = "crit" | "hit" | "missNone" | "missDamage" | "saveHalf" | "saveFail" | "pc"; -type Rounding = "none" | "floor" | "round" | "ceil"; -/** How a d20 attack roll resolves: single die, keep-highest of 2/3, or keep-lowest of 2. */ -type RollType = "flat" | "advantage" | "disadvantage" | "elven accuracy"; -/** - * P(critical hit) for the given crit window and d20 {@link RollType}. - * - * `critRange` is the number of top faces that crit (1 for a natural 20, 2 for - * 19–20, …), so a single die crits with probability `critRange / 20`. Advantage - * rolls two d20s / elven accuracy three, keeping the best; disadvantage keeps - * the worst of two. - */ -declare function critProbability(critRange: number, rollType?: RollType): number; -/** - * The canonical "clean miss" outcome — a point of zero damage with no rider. - * This is the {@link OutcomeType} that attribution charts and outcome stats key - * on, and is distinct from the builder's attack-resolution `miss` weight label. - */ -declare const MISS_NONE_OUTCOME: OutcomeType; -/** - * All outcome types in canonical severity order — clean miss → crit. This is - * also the natural stacking order for attribution charts (least- to - * most-impactful, bottom → top). Enumerates every {@link OutcomeType} exactly - * once; use it instead of hand-maintained per-consumer outcome tables. - */ -declare const ALL_OUTCOME_TYPES: OutcomeType[]; -/** - * Outcome types in display order for stats / breakdown rows — most prominent - * first (crit, hit, …) down to the clean miss. - */ -declare const OUTCOME_DISPLAY_ORDER: OutcomeType[]; -/** - * Sort outcome labels by a canonical order (defaults to {@link ALL_OUTCOME_TYPES}). - * Labels not present in `order` sort after known ones, alphabetically — so - * ad-hoc/test labels outside the {@link OutcomeType} union stay stable. - */ -declare function sortOutcomes(outcomes: Iterable, order?: readonly string[]): T[]; -declare const onAnyHit: OutcomeType[]; -declare const onCritOnly: OutcomeType[]; -declare const onHitOnly: OutcomeType[]; -declare const onMissOnly: OutcomeType[]; -declare const onMissDamageOnly: OutcomeType[]; -declare const onSaveHalfOnly: OutcomeType[]; -declare const onSaveFailOnly: OutcomeType[]; -declare const onPotentCantripOnly: OutcomeType[]; - -/** - * Query interface for analyzing dice roll probability distributions. - * - * Combines multiple attack PMFs and provides statistical analysis methods for: - * - Basic statistics (mean, variance, min/max, percentiles) - * - Probability queries (hit chances, success rates, exact counts) - * - Damage analysis (ranges by outcome type, expected values) - * - Data export (charts, tables, visualizations) - * - */ -declare class DiceQuery { - readonly singles: PMF[]; - private readonly _eps; - private readonly _combinedProvided; - private _combined?; - private _combinedWithAttr?; - constructor(singles: PMF | PMF[], combined?: PMF, eps?: number); - /** - * The combined damage distribution of all single PMFs (their convolution), - * normalized to total probability 1. - * - * Computed lazily on first access and cached. Queries that only need - * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance}, - * {@link DiceQuery.stddev} — never trigger this convolution. - */ - get combined(): PMF; - private static readonly DEFAULT_OUTCOMES; - /** - * Returns a new PMF with damage attribution metadata populated. - * - * This method computes attribution on-demand for builder-generated PMFs, - * enabling them to work with damage attribution charts. The `attr` field - * tracks how much damage each outcome type contributes at each damage value. - * - * For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D) - * - * Performance: Cached after first call. Adds minimal overhead vs `combined`. - * - * @returns PMF with attr field populated for damage attribution charts - * - * @example - * const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6)) - * const query = attack.toQuery() - * const pmf = query.combinedWithAttribution() - * // Now pmf can be used with toDamageAttributionChartSeries() - */ - combinedWithAttribution(): PMF; - /** - * Per-label `damage value → probability mass` series for the combined, - * attribution-carrying distribution — the provenance core of the stacked - * damage-attribution chart. Convenience for - * `combinedWithAttribution().attributionByValue()`; see - * {@link PMF.attributionByValue}. - */ - attributionByValue(): Map>; - /** - * How many of the independent single PMFs can produce the given outcome - * label. Useful for "all of them succeeded" style probabilities where the - * exponent is the number of contributing attacks (see - * {@link DiceQuery.probExactlyK}). - */ - countSinglesWith(label: string): number; - /** - * Returns the expected damage across all possible outcomes. - * - * Example: `query.mean()` → 12.5 - * Use case: "What's my average damage per round?" - */ - mean(): number; - /** - * Returns the variance of the damage distribution. - * - * Example: `query.variance()` → 45.2 - * Use case: "How much does my damage vary from the average?" - * High variance means higher risk/reward. Lower variance means more consistent damage. - */ - variance(): number; - /** - * Returns the standard deviation of the damage distribution. - * - * Example: `query.stdev()` → 6.7 - * Use case: "What's the typical spread around my average damage?" - * Used to determine how consistent the damage is. - */ - stddev(): number; - /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */ - stdev(): number; - /** - * Returns the Cumulative Distribution Function. - */ - cdf(x: number): number; - /** - * Returns the probability of dealing X damage or less. - * In statistics, this is called the cumulative distribution function (CDF). - * Example: `query.cdf(20)` → 0.75 - * Use case: "What's the chance I deal 20 damage or less?" - */ - probTotalAtMost(x: number): number; - /** - * Returns the Complementary Cumulative Distribution Function. - */ - ccdf(x: number): number; - /** - * Returns the probability of dealing at least X damage. - * - * Example: `query.probTotalAtLeast(25)` → 0.35 - * Use case: "What's the chance I deal at least 25 damage to finish the enemy?" - */ - probTotalAtLeast(threshold: number): number; - /** - * Returns damage values at specific percentiles. - * - * Example: `query.percentiles([0.25, 0.5, 0.75])` → [8, 12, 18] - * Use case: "What are my 25th, 50th, and 75th percentile damage values?" - */ - percentiles(percentileValues: number[]): number[]; - /** - * Returns the minimum possible damage. - * - * Example: `query.min()` → 0 - * Use case: "What's the worst-case damage if everything misses?" - */ - min(): number; - /** - * Returns the maximum possible damage. - * - * Example: `query.max()` → 56 - * Use case: "What's the best-case damage if everything crits and rolls max?" - */ - max(): number; - private singleProb; - /** - * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it - * carries ANY of `labels`", over the n independent singles. - * - * Each single's per-event success probability is the Poisson-binomial - * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne), - * computed exactly once. The binomial DP then runs once to produce the whole - * distribution, so the array-label paths of probExactlyK / probAtLeastK / - * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and - * re-running the DP per requested k. - */ - private countDistribution; - probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number; - /** - * Returns the probability that at least one attack has the specified outcome(s). - * - This is the complement of probAtMostK(labels, 0) - * - * Examples: - * - `query.probAtLeastOne('hit')` → 0.88 (88% chance at least one attack hits) - * - `query.probAtLeastOne(['hit', 'crit'])` → 0.96 (96% chance at least one succeeds) - * - * Use cases: - * - "What's the chance at least one of my attacks connects?" - * - * Note: - * - * - You have to pass in an array of labels to avoid double-counting if you are - * using multiple labels. You cannot just add them. - */ - probAtLeastOne(labels: OutcomeType | OutcomeType[]): number; - /** - * Computes binomial probabilities for exactly 0, 1, 2, ..., maxK occurrences of a label. - * - * Uses dynamic programming to efficiently calculate the probability distribution - * of how many attacks will have the specified outcome, accounting for different - * success probabilities across individual attacks. - * - * Example: For 3 attacks with 50% hit chance each, returns: - * [0.125, 0.375, 0.375, 0.125] = [P(0 hits), P(1 hit), P(2 hits), P(3 hits)] - * - * @param label - The outcome type to count - * @param maxK - Maximum number of occurrences to calculate (usually number of attacks) - * @returns Array where index K contains P(exactly K attacks have the label) - */ - private computeBinomialProbabilities; - /** - * Returns the probability that exactly K attacks result in the specified outcome(s). - * - * Single label examples: - * - probExactlyK('hit', 2) = probability exactly 2 attacks hit - * - probExactlyK('crit', 1) = probability exactly 1 attack crits - * - probExactlyK('crit', 0) = probability no attacks crit - * - * Array examples: - * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed - * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds - * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss - * - * Use cases: - * - "What's the chance exactly one of my attacks hits?" - * - "How likely am I to get exactly 2 successes out of 3 attacks?" - * - "What's the probability that exactly half my attacks succeed?" - * - * Note: For arrays, an attack counts as a "success" if it has any of the specified labels. - * This is different from probAtMostK, which counts an attack as a "success" if it has ALL of the specified labels. - */ - probExactlyK(labels: OutcomeType | OutcomeType[], k: number): number; - /** - * Returns the probability that AT MOST K attacks result in the specified outcome(s). - * - * Single label examples: - * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1) - * - probAtMostK('crit', 0) = probability no attacks crit - * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss - * - * Array examples: - * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds - * - probAtMostK(['hit', 'crit'], 0) = probability no attacks succeed (all miss) - * - * Use cases: - * - "What's the chance that at most one attack hits?" (rest miss) - * - "How likely am I to have mostly failures?" (at most 1 success) - * - "What's the probability of a really bad turn?" (at most 0 successes) - * - */ - probAtMostK(labels: OutcomeType | OutcomeType[], k: number): number; - /** - * Returns the expected damage attributed to specific outcome types. - * - * Single label examples: - * - expectedDamageFrom('hit') = expected damage from hit components - * - expectedDamageFrom('crit') = expected damage from crit components - * - * Array examples: - * - expectedDamageFrom(['hit', 'crit']) = expected damage from any success - * - expectedDamageFrom(['missDamage', 'missNone']) = expected damage from misses - * - * Use cases: - * - "How much damage do I expect from successful attacks?" - * - "What's the damage contribution from critical hits specifically?" - * - "How much damage comes from miss effects (like save-for-half spells)?" - */ - expectedDamageFrom(labels: OutcomeType | OutcomeType[]): number; - /** - * Returns damage statistics for scenarios where AT LEAST ONE attack results in - * the specified outcome(s). - * - * This method answers "What happens when things go reasonably well?" rather than - * "What's the theoretical maximum?" It includes mixed scenarios which are more - * common and tactically relevant than pure scenarios. - * - * Single label examples: - * - damageStatsFrom('hit') = damage range when at least one attack hits - * - damageStatsFrom('crit') = damage range when at least one attack crits - * - * Array examples: - * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds - * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses - * - * Tactical Use Cases: - * - "Given that I don't completely whiff (99% of turns), what damage should I expect?" - * - "When planning to kill a 60 HP enemy, what's my damage range on successful turns?" - * - "Should I use this risky spell if it has good damage when it works?" - * - "What's my damage potential when something goes right?" (vs pure failure) - * - * Combat Planning Examples: - * - 4 attacks with 90% hit chance: "96% of the time you'll do 25-150 damage, avg 52" - * (Much more useful than "You average 50 damage including complete misses") - * - Risk assessment: "80% of successful turns do 40-80 damage, but 20% do 80-150" - * - Resource management: "If I hit anything, I'll likely finish this enemy" - * - * Statistical Note: - * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which - * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats. - * - * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an - * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and - * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than - * E[dmg | the label occurs]. For a single attack both are the plain - * conditional figures. Use {@link probAtLeastOne} for the scenario probability. - * - * @example - * // High-level tactical planning - * const successStats = query.damageStatsFrom('hit') - * const successChance = query.probAtLeastOne('hit') - * console.log(`${(successChance*100).toFixed(1)}% chance to do ${successStats.min}-${successStats.max} damage`) - */ - damageStatsFrom(labels: OutcomeType | OutcomeType[]): { - min: number; - max: number; - avg: number; - count: number; - }; - /** - * Returns damage statistics for scenarios where ALL attacks result in the specified - * outcome, calculated by leveraging the pure partition of singles. - * - * This method answers "What's the theoretical best/worst case?" and "What are the - * clean mathematical boundaries?" It provides pure scenarios without mixing outcomes. - * - * Examples: - * - combinedDamageStats('hit') = damage range when all attacks hit (none crit, none miss) - * - combinedDamageStats('crit') = damage range when all attacks crit (none just hit) - * - * UI and Display Use Cases: - * - Statistics panels showing "MAX Hit Damage" (users expect pure hits, not mixed) - * - "Best case scenario" vs "worst case scenario" analysis - * - Mathematical verification: "Does our hit damage calculation match manual math?" - * - Clean damage type attribution: "How much comes from base hits vs crits?" - * - * Design and Balance Use Cases: - * - Game designers: "What's the damage ceiling if someone gets lucky?" - * - Character optimization: "What's my absolute maximum potential?" - * - Ability comparison: "Which build has higher crit ceiling?" - * - Minimum guaranteed damage: "What's the worst I can do if everything hits?" - * - * Mathematical Use Cases: - * - Validating complex calculations against simple manual math - * - Understanding damage component contributions in isolation - * - Separating luck (crit variance) from consistency (hit variance) - * - Building intuition about damage sources - * - * When to Use This vs damageStatsFrom(): - * - Use THIS for: UI max/min displays, theoretical limits, clean comparisons - * - Use damageStatsFrom() for: tactical planning, realistic expectations, mixed scenarios - * - * Statistical Note: - * Pure scenarios (all hits, all crits) are rare but represent clear mathematical - * boundaries. These stats help understand the "shape" of your damage potential. - * - * @example - * // UI display logic - * const pureHitMax = query.combinedDamageStats('hit').max // Clean "MAX Hit Damage: 90" - * const pureCritMax = query.combinedDamageStats('crit').max // Clean "MAX Crit Damage: 168" - * - * // vs tactical planning (use damageStatsFrom instead) - * const realisticRange = query.damageStatsFrom('hit') // Includes mixed scenarios - */ - combinedDamageStats(targetLabel: OutcomeType): { - min: number; - max: number; - avg: number; - count: number; - }; - /** - * Returns the probability that at least one attack carries ANY of the - * specified labels (the marginal P(≥1) across the independent attacks). - * - * Examples: - * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs) - * - `query.probabilityOf(['hit', 'crit'])` → 0.96 (probability of any success) - * - * Use cases: - * - "What's the chance my resolution includes a success label?" - * - "How likely am I to get any hits or crits across all attacks?" - * - * Note: this must NOT be computed by summing `combined` bin probabilities. A - * single combined damage total is reachable by many outcome combinations and - * a bin can hold several labels at once, so summing `bin.p` over bins that - * contain a label over-counts. The correct marginal is the Poisson-binomial - * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}. - */ - probabilityOf(labels: OutcomeType | OutcomeType[]): number; - /** - * Returns the probability of missing (any type of miss). - * - * Example: `query.missChance()` → 0.04 - * Use case: "What's the chance I miss completely this turn?" - */ - missChance(): number; - /** - * Returns data formatted for plotting damage probability distribution. - * - * Example: `query.toChartSeries()` → [{x: 0, y: 0.04}, {x: 6, y: 0.1}, ...] - * Use case: "I want to visualize my damage distribution in a chart." - */ - toChartSeries(): Array<{ - x: number; - y: number; - }>; - /** - * Returns tabular data showing damage values and their probability breakdowns. - * - * Example: `query.toLabeledTable(['hit', 'crit'])` → - * [{damage: 6, total: 0.01, hit: 0.008, crit: 0}, ...] - * - * Use case: "I want to see exactly how hit/crit probabilities contribute to each damage value." - */ - toLabeledTable(labels?: OutcomeType[]): Array<{ - damage: number; - total: number; - } & Record>; - /** - * Returns data for stacked charts with unconditional per-label probability mass per damage. - * - * - Each dataset value equals the unconditional probability mass for that label at that damage - * (i.e., `bin.count[label]`). - * - Column sums may be less than the total probability `bin.p` when you omit labels or when - * there is unlabeled mass. Include all relevant outcome labels if you need the sum to match. - * - This behavior matches tests that expect raw per-label mass (not proportional scaling). - * - NOTE: This implementation may break dprcalc.com chart binning at large n, need to test it more. - * - * @example - * query.toStackedChartData(['hit', 'crit']) - * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]} - */ - toStackedChartData(labels?: OutcomeType[], epsilon?: number): { - labels: number[]; - datasets: Array<{ - label: string; - data: number[]; - }>; - }; - /** - * Returns pure mathematical data for attribution charts showing outcome contributions. - * - * Automatically discovers all outcome types present in the PMF, applies filtering rules, - * and returns proportional data suitable for stacked visualization. - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and proportional data - * - * @example - * query.toAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [5.2, 8.1, ...], crit: [0, 2.3, ...]}} - */ - toAttributionChartSeries(options?: { - stackOrder?: string[]; - filterRules?: (outcome: string, damage: number) => boolean; - asPercentages?: boolean; - }): { - support: number[]; - outcomes: string[]; - data: { - [outcome: string]: number[]; - }; - }; - /** - * Returns pure mathematical data for damage attribution charts showing damage contribution - * from each outcome type at each damage value. - * - * Similar to toAttributionChartSeries() but uses bin.attr (damage attribution) instead of - * bin.count (probability attribution). - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or raw damage values (0+) - * @returns Pure data structure with support, outcomes, and damage attribution data - * - * @example - * query.toDamageAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['hit', 'crit'], data: {hit: [3.2, 5.1, ...], crit: [0, 1.8, ...]}} - */ - toDamageAttributionChartSeries(options?: { - stackOrder?: string[]; - filterRules?: (outcome: string, damage: number) => boolean; - asPercentages?: boolean; - }): { - support: number[]; - outcomes: string[]; - data: { - [outcome: string]: number[]; - }; - }; - /** - * Returns pure mathematical data for outcome attribution charts showing which - * attack outcome combinations can produce each damage value. - * - * Unlike toDamageAttributionChartSeries() which tracks damage sources, this tracks - * outcome combinations - answering "what attack outcomes produced this damage?" - * - * @param options Configuration options - * @param options.stackOrder Preferred order for outcome types (unknowns placed at end) - * @param options.filterRules Function to determine if outcome should be included for a given damage value - * @param options.asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support, outcomes, and outcome combination probabilities - * - * @example - * query.toOutcomeAttributionChartSeries() - * // → {support: [0, 6, 12], outcomes: ['all_miss', 'mixed', 'all_hit'], data: {all_miss: [15, 0, 0], mixed: [60, 80, 20], all_hit: [25, 20, 80]}} - */ - toOutcomeAttributionChartSeries(options?: { - stackOrder?: string[]; - filterRules?: (outcome: string, damage: number) => boolean; - asPercentages?: boolean; - }): { - support: number[]; - outcomes: string[]; - data: { - [outcome: string]: number[]; - }; - }; - /** - * Returns pure mathematical data for cumulative distribution function (CDF). - * Shows P(X ≤ x) - the probability of getting at most x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and cumulative probabilities - * - * @example - * query.toCDFSeries() - * // → {support: [0, 6, 12], data: [5.2, 18.3, 45.1]} - */ - toCDFSeries(asPercentages?: boolean): { - support: number[]; - data: number[]; - }; - /** - * Returns pure mathematical data for complementary cumulative distribution function (CCDF). - * Shows P(X ≥ x) - the probability of getting at least x damage. - * - * @param asPercentages Whether to return percentages (0-100) or probabilities (0-1) - * @returns Pure data structure with support and complementary cumulative probabilities - * - * @example - * query.toCCDFSeries() - * // → {support: [0, 6, 12], data: [100, 94.8, 81.7]} - */ - toCCDFSeries(asPercentages?: boolean): { - support: number[]; - data: number[]; - }; - /** Probability of doing strictly more than threshold damage (default >0). */ - probDamageGreaterThan(threshold?: number): number; - /** All outcome keys actually present (typed & ordered if you pass an order). */ - outcomeKeys(order?: OutcomeType[]): OutcomeType[]; - /** Total probability per outcome across the PMF. */ - outcomeTotals(outcomes?: OutcomeType[]): Map; - /** Conditional damage range per outcome (min/avg/max of X | outcome). */ - outcomeDamageRanges(outcomes?: OutcomeType[]): Map; - /** - * Snapshot of the distribution in the exact shape the UI consumes. - * - outcome probabilities are "at least one" (and equal to "all" for a single PMF) - * - damageRange is conditional on the outcome occurring - * - * The outcome probabilities use the correct Poisson-binomial marginals - * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)), - * so they are always valid probabilities in [0,1]. - * - * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from - * the combined PMF's `count`, which the convolution accumulates as an EXPECTED - * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label] - * rather than a clean conditional expectation. It is correct for a single - * attack. - */ - snapshot(order?: readonly OutcomeType[]): Snapshot; - /** - * PMF Transformation Methods - * - * These methods provide a fluent API for transforming dice queries by wrapping - * the underlying PMF transformation methods. All operations work on the combined - * PMF and return new DiceQuery instances. - */ - /** - * Returns a new DiceQuery with normalized probabilities (ensuring they sum to 1.0). - * - * @returns New DiceQuery with normalized combined PMF - */ - normalize(): DiceQuery; - /** - * Returns a new DiceQuery with low-probability outcomes removed. - * - * @param eps Minimum probability threshold (defaults to PMF epsilon) - * @param keepFinalBin Whether to keep the highest damage bin regardless of probability - * @returns New DiceQuery with compacted combined PMF - */ - compact(eps?: number, keepFinalBin?: boolean): DiceQuery; - /** - * Returns a new DiceQuery with an additional scaled branch added. - * Useful for conditional outcomes like "30% chance of opportunity attack". - * - * @param branch DiceQuery to add as a scaled branch - * @param probability Probability of the branch occurring (0-1) - * @returns New DiceQuery combining this query with the scaled branch - * - * @example - * const baseAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const opportunityAttack = parse("(d20 + 5 AC 15) * (1d8 + 3)"); - * const withOpportunity = baseAttack.addScaled(opportunityAttack, 0.3); - */ - addScaled(branch: DiceQuery, probability: number): DiceQuery; - /** - * Returns a new DiceQuery with all probabilities scaled by a factor. - * Used for conditional scenarios where the entire outcome has reduced probability. - * - * @param factor Scaling factor for probabilities - * @returns New DiceQuery with scaled probabilities - * - * @example - * const fullAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const conditionalAttack = fullAttack.scaleMass(0.3); // 30% chance scenario - */ - scaleMass(factor: number): DiceQuery; - totalMass(): number; - /** - * Returns a new DiceQuery with damage values transformed by a function. - * Useful for applying modifiers, resistances, or other damage transformations. - * - * @param damageTransformFunction Function to transform each damage value - * @returns New DiceQuery with transformed damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const withResistance = baseAttack.mapDamage(dmg => Math.floor(dmg / 2)); // Half damage - * const withBonus = baseAttack.mapDamage(dmg => dmg + 5); // +5 damage - */ - mapDamage(damageTransformFunction: (damageValue: number) => number): DiceQuery; - /** - * Returns a new DiceQuery with damage values scaled by a factor. - * Convenient wrapper around mapDamage for multiplicative scaling. - * - * @param factor Scaling factor for damage values - * @param rounding Rounding method: "floor" (default), "round", or "ceil" - * @returns New DiceQuery with scaled damage values - * - * @example - * const baseAttack = parse("2d6 + 3"); - * const doubled = baseAttack.scaleDamage(2); // Double damage - * const halfDamage = baseAttack.scaleDamage(0.5, "round"); // Half damage, rounded - */ - scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): DiceQuery; - /** - * Returns a new DiceQuery combining this query with another via convolution. - * Equivalent to rolling both queries independently and adding results. - * It is important to use this rather than combing()ing the PMFs directly! - * This method maintains the provenance of the PMFs which is needed for damage attribution. - * Combining the .combined PMFs directly is still valid for DPR calculations but - * is not statistically sound for queries. - * - * @param other DiceQuery to combine with - * @param eps Optional epsilon for precision control - * @returns New DiceQuery representing the combined outcome - * - * @example - * const mainAttack = parse("(d20 + 5 AC 15) * (2d6 + 3)"); - * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)"); - * const bothAttacks = mainAttack.convolve(bonusAttack); - */ - convolve(other: DiceQuery): DiceQuery; - /** - * First-success split over an ordered list of DISTINCT single-swing PMFs. - * Each PMF may have different success/subset probabilities (from labels). - * - * successOutcome: e.g., ["success"] or ["hit", "crit"] - * subsetOutcome: e.g., ["subset"] or ["crit"] where subset ⊆ success - * - * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone] - */ - firstSuccessSplit(successOutcome: OutcomeType | OutcomeType[], subsetOutcome: OutcomeType | OutcomeType[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number]; -} -type OutcomeSnapshot = { - atLeastOneProbability: number; - allProbability: number; - damageRange: { - min: number; - avg: number; - max: number; - }; -}; -type Snapshot = { - averageDPR: number; - damageChance: number; - percentiles: { - p25: number; - p50: number; - p75: number; - }; - outcomes: Map; -}; - -declare const pmfCache: LRUCache; -/** - * Probability Mass Function for discrete damage distributions. - */ -declare class PMF { - readonly map: Map; - readonly epsilon: number; - readonly normalized: boolean; - readonly identifier: string; - private _preservedProvenance; - private static __anonIdCounter; - private _support?; - private _min?; - private _max?; - private _totalMass?; - private _mean?; - private _variance?; - private _stdev?; - private _fingerprint?; - constructor(map?: Map, epsilon?: number, normalized?: boolean, identifier?: string, _preservedProvenance?: boolean); - static empty(epsilon?: number, identifier?: string): PMF; - static zero(epsilon?: number): PMF; - static delta(value: number, epsilon?: number): PMF; - /** - * Point mass at damage 0 tagged with the canonical `missNone` outcome. - * - * Differs from {@link PMF.zero}, which labels its zero bin `miss` — the - * builder's attack-resolution vocabulary. This uses the `missNone` - * {@link OutcomeType} that the attribution charts and outcome stats key on, - * so it is the correct "clean miss / no damage" delta for provenance-aware - * mixtures feeding those consumers. - */ - static missNone(epsilon?: number): PMF; - static emptyMass(): PMF; - [Symbol.iterator](): IterableIterator<[number, Bin]>; - static clearCache(): void; - /** - * Creates a conditional PMF from two branches (success and failure) and a probability. - * This is the core logic for modeling any probabilistic event where there are two - * distinct outcomes. - */ - static branch(successPMF: PMF, failurePMF: PMF, successProbability: number): PMF; - /** - * withProbability() - * - * A convenience wrapper around branch() for the common case where the "failure" branch is always zero(). - * - * Think of this as a shortcut for: - * pmf.gate(p, PMF.zero()) - * - * Use this to model a *single* Bernoulli event — an outcome that either happens or doesn't, - * like an opportunity attack that occurs with probability p, or a single attack that either hits or misses. - * - * This is **not** for combining multiple independent attacks or mutually exclusive multi-outcome scenarios. - * - For multiple independent swings, use DiceQuery with separate PMFs for each attack. - * - For modeling "first success" logic across multiple attacks (like Sneak Attack or Smite) - * use query.firstSuccessSplit() to get the exact probabilities. - * - For scenarios with several mutually exclusive outcomes (like crit vs hit vs none), use PMF.exclusive(). - * - */ - static withProbability(successPMF: PMF, probability: number): PMF; - /** - * gate() - * - * A conditional wrapper around branch() that applies this PMF with probability `p`, - * and applies a provided fallback PMF otherwise. - * - * This is useful for modeling a binary choice between two outcomes: - * - The "success" outcome (this PMF) happens with probability `p`. - * - The "failure" outcome (fallback PMF) happens with probability `1 - p`. - * - * Examples: - * - 25% chance to include an opportunity attack, otherwise nothing: - * attackPMF.gate(0.25, PMF.zero()) - * - * - 50% chance to deal fireball damage, otherwise cone of cold damage: - * fireballPMF.gate(0.5, coneOfColdPMF) - * - * Relationship to other helpers: - * - **withProbability()** is a shortcut for the common case where the fallback is `PMF.zero()`. - * - **exclusive()** is for three or more mutually exclusive outcomes (e.g., crit vs hit vs none). - * - * @param p Probability of applying this PMF (between 0 and 1). - * @param fallback PMF to apply when this PMF is *not* selected. - * @returns A new PMF representing the weighted mixture of this PMF and the fallback. - */ - gate(p: number, fallback: PMF): PMF; - /** - * PMF.exclusive() - * - * Builds a single PMF from a set of mutually exclusive weighted outcomes. - * Exactly one of the provided options will occur. - * - * Each option has: - * - A PMF representing its outcome (e.g., damage dice). - * - A weight representing its probability of being selected. - * - * Notes: - * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero() - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for floating point rounding. - */ - static exclusive(options: Array<{ - pmf: PMF; - weight: number; - } | [PMF, number]>, eps?: number): PMF; - /** - * PMF.mix() - * - * Builds a PMF as a linear combination of input PMFs with the given weights. - * Unlike `exclusive`, this does NOT: - * - enforce that weights sum to 1 - * - add leftover probability to δ0 (PMF.zero()) - * - * Use when outcomes are not mutually exclusive, or for interpolation/blending. - * - * @param options Array of `{ pmf, weight }` or `[PMF, number]`. - * @param eps Optional tolerance for skipping tiny weights. - */ - static mix(options: Array<{ - pmf: PMF; - weight: number; - } | [PMF, number]>, eps?: number): PMF; - /** - * Adds damage attribution metadata to this PMF based on existing count metadata. - * For each bin, sets attr[outcome] = damage × count[outcome]. - * - * This enables damage attribution charts to work with builder-generated PMFs. - * The parser generates attr automatically, but builder PMFs only have count. - * - * @returns New PMF with attr field populated in each bin - */ - /** - * Returns true if this PMF already carries damage attribution metadata. - * - * Only the first positive-damage bin is inspected (parser-generated PMFs - * populate `attr` uniformly), so this is O(1) in practice. - */ - hasAttribution(): boolean; - withAttribution(): PMF; - /** - * General-purpose N-way mixture. - * weights: Array of [weight, PMF]. - * - * Example: PMF.mixN([ - * [pMiss, zero], - * [pHit, hitPMF], - * [pCrit, critPMF], - * ]); - */ - static mixN(weights: [number, PMF][], eps?: number): PMF; - private setPreservedProvenance; - preservedProvenance(): boolean; - private getPowerCacheKey; - /** - * Efficiently computes this PMF convolved with itself `n` times. - * Uses exponentiation by squaring to reduce total convolutions. - * n must be a positive integer. - * * - * * NOTE: This folds multiple independent attacks into a single PMF. - * As a result, The power() method causes a loss of data provenance. - * This is ONLY SAFE if you are trying to calculate masses. - * If you want to query any atLeast probabilities, you should use the DiceQuery class instead without power(). - */ - power(n: number, eps?: number): PMF; - replicate(n: number): PMF[]; - mass(): number; - outcomeMass(outcome: string): number; - faceTotal(): number; - normalize(): PMF; - /** - * Returns a copy with negligible probabilities removed (p < eps). - * If keepFinalBin is true, the bin with the largest key is always kept, - * even if its probability is below eps. count/attr submaps are still cleaned. - */ - compact(eps?: number, keepFinalBin?: boolean): PMF; - support(): number[]; - min(): number; - max(): number; - /** - * Returns the expected (mean) damage value. - * Cached for performance since this requires iterating through all bins. - */ - mean(): number; - /** - * Returns the variance of the damage distribution. - * Cached for performance since this requires mean calculation plus iteration. - */ - variance(): number; - /** - * Returns the standard deviation of the damage distribution. - */ - stdev(): number; - /** Deep-copies a Bin, cloning its count and (optional) attr maps. */ - private static cloneBin; - /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */ - private static scaleBin; - private static mergeInto; - add(other: PMF): PMF; - /** - * Returns a new PMF with a scaled branch added to this one. - * The branch PMF is scaled by the given probability before merging - * This will be very useful for conditional effects and for being - * able to model "I can probably have this opportunity attack 40% of rounds" - * Example: `pmf.addScaled(critBranch, 0.05)` → PMF including 5% crit outcomes - */ - addScaled(branch: PMF, probability: number): PMF; - /** - * Redistributes probability mass to model an effect that only occurs with - * probability `frequency` — a conditional attack, an on-hit rider, or a - * sub-one AoE target fraction. - * - * Every hit outcome (damage > 0) is scaled by `frequency` — probability mass, - * per-label `count`, AND per-label `attr` — and the freed mass is moved into - * the miss bin at damage 0, tagged with the canonical `missNone` outcome. - * Total probability mass is preserved. - * - * Unlike a bare {@link scaleMass} or {@link mapDamage}, this keeps damage - * attribution (`attr`) intact, so a frequency-scaled PMF still renders - * correctly in the damage-attribution charts. - * - * `frequency >= 1` (or non-finite) returns this PMF unchanged; `frequency <= 0` - * collapses all mass into the miss bin. The miss outcome is assumed to be - * encoded at damage value 0. - * - * @param frequency Probability in [0, 1] that the effect occurs. - */ - applyHitFrequency(frequency: number): PMF; - scaleMass(factor: number): PMF; - mapDamage(damageTransformFunction: (damageValue: number) => number): PMF; - scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): PMF; - private getPMFCombineCacheKey; - /** - * A small content fingerprint (mass + bin count + face sum) so convolution - * cache keys change if the underlying numbers do. Memoized because a PMF is - * immutable once constructed — this avoids re-summing every key on each - * convolve() call (including cache hits). - */ - fingerprint(): string; - convolve(other: PMF, eps?: number, raw?: boolean): PMF; - combineRaw(other: PMF, eps?: number): PMF; - private static reduceConvolveLeft; - /** - * Convolves multiple PMFs using linear convolution with automatic caching. - * Uses a left-to-right accumulation approach for maximum cache reuse. - * Each convolve() call automatically uses the convolution cache for performance. - * - * This linear approach provides better cache hits than pairwise because: - * - Intermediate results are more predictable and stable - * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.) - * - Order-independent cache keys work better with consistent build patterns - */ - static convolveMany(pmfList: PMF[], eps?: number): PMF; - /** - * Returns a plain, JSON-serializable representation of this PMF. - * - * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces - * the expected output (no double-encoding). Use {@link PMF.fromJSON} to - * reconstruct, or {@link PMF.toJSONString} if you need the string directly. - */ - toJSON(): { - bins: Array<[number, Bin]>; - normalized: boolean; - identifier: string; - }; - /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */ - toJSONString(): string; - static fromJSON(jsonData: { - bins: Array<[number, Bin]>; - normalized?: boolean; - identifier?: string; - }): PMF; - /** - * Relative pruning with optional top-K floor. - * Keeps bins with p >= epsRel * peak, always keeps min and max damage, - * optionally guarantees at least `minBins` survivors by adding top-K. - * Returns a new, non-normalized PMF. - */ - prune(epsRel: number, minBins?: number): PMF; - /** Probability mass at exactly x. */ - pAt(x: number): number; - /** - * P(any damage) — the mass on all non-zero outcomes, i.e. `1 - P(0)`. - * Assumes a miss is encoded as the damage-0 bin (the convention used across - * attack/save PMFs). The dual of {@link missProbability}. - */ - hitProbability(): number; - /** P(no damage) — the mass at damage 0. The dual of {@link hitProbability}. */ - missProbability(): number; - /** - * Coarsen the distribution into at most `maxBuckets` contiguous, equal-width - * damage buckets, aggregating probability mass (and `count`/`attr` - * provenance) into each bucket's start value. Returns this PMF unchanged when - * its integer support already fits within `maxBuckets`. - * - * This is a lossy display/downsampling transform (bucket start replaces the - * exact damage value) — use it for charting wide distributions, not for DPR - * math. - */ - rebin(maxBuckets: number): PMF; - /** Dense integer support from min..max (inclusive). - * Useful for showing empty bars in charts. - */ - denseSupport(): number[]; - /** CDF at x: P(X ≤ x). */ - cdfAt(x: number): number; - /** Quantile / inverse CDF for p in [0,1]. Returns smallest x with CDF ≥ p. */ - quantile(p: number): number; - /** Get outcome probability at specific damage value. */ - outcomeAt(damage: number, outcome: string): number; - /** Get all outcome types present in this PMF. */ - outcomes(): string[]; - /** Get total probability of an outcome across all damage values. */ - outcomeProbability(outcome: string): number; - /** Get damage attribution for an outcome at specific damage value. */ - outcomeAttributionAt(damage: number, outcome: string): number; - /** Get all outcome data at specific damage value. */ - binAt(damage: number): { - p: number; - count: Record; - attr?: Record; - } | null; - /** Check if outcome exists in this PMF. */ - hasOutcome(outcome: string): boolean; - /** - * Split each damage value's probability mass across outcome labels, returning - * per-label maps of `damage value → probability mass attributable to that - * label`. Summing over labels at a given value recovers that value's `p`. - * - * Damage-bearing bins are split by `attr` weight (the share of damage each - * outcome contributed); the clean-miss bin at 0 is split by `count` weight - * (there is no damage to attribute). Attribution is computed on demand via - * {@link withAttribution} when absent, so builder-generated PMFs work too. - * - * This is the provenance core of the stacked damage-attribution chart — the - * caller only maps these series into its rendering format (colors, binning, - * axis labels). - */ - attributionByValue(): Map>; - tailProbGE(t: number): number; - tailProbGT(t: number): number; - /** - * Returns a new PMF containing only bins where the specified outcome has non-zero probability. - * This creates a marginal distribution for the given outcome type, with probabilities - * scaled to represent the unconditional mass attributable to that outcome. - */ - filterOutcome(outcome: string): PMF; - /** - * Calculates probabilities for first-success outcomes across n independent attempts. - * - * @param pSuccess - Total probability of any success on a single attempt. - * @param pSpecial - Probability of a specific subset of successes (e.g., critical success). - * @param n - Number of independent attempts. - * - * Returns: - * - pSpecificSuccess: Probability that the first success was of the "special" type - * - pGeneralSuccess: Probability that the first success was of the non-special type - * - pNone: Probability that no successes occurred - * - pAny: Probability that at least one success occurred - */ - static firstSuccessWeights(pSuccess: number, pSpecial: number, n: number): { - pSpecificSuccess: number; - pGeneralSuccess: number; - pNone: number; - pAny: number; - }; - mapValues(f: (v: number) => number, eps?: number, opts?: { - rounding?: Rounding; - preserveCounts?: boolean; - }): PMF; - static fromMap(m: Map, eps?: number, { requireIntegerValues }?: { - requireIntegerValues?: boolean; - }): PMF; - query(): DiceQuery; -} - -export { ALL_OUTCOME_TYPES as A, type Bin as B, type CritConfig as C, type DamageDistribution as D, EPS as E, LRUCache as L, MISS_NONE_OUTCOME as M, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type OutcomeType as a, type RollType as b, critProbability as c, OUTCOME_DISPLAY_ORDER as d, onCritOnly as e, onHitOnly as f, onMissOnly as g, onMissDamageOnly as h, onSaveHalfOnly as i, onSaveFailOnly as j, onPotentCantripOnly as k, DiceQuery as l, type OutcomeSnapshot as m, onAnyHit as o, pmfCache as p, sortOutcomes as s }; From 62bfa0ebd17bfd8a6f7f199b9c3d6f3d788fb1b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 07:19:00 +0000 Subject: [PATCH 4/4] chore(release): 0.5.0 Bump version to 0.5.0 and roll the unreleased 0.4.0 changelog into it, documenting the composable scaleResult / sumRolls API alongside the existing PMF provenance work. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX --- CHANGELOG.md | 21 ++++++++++++++++++--- package.json | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e75a72..5d20b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,30 @@ All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.4.0] +## [0.5.0] Pushes damage-attribution / provenance and D&D-probability logic that the consuming app (dprcalc) had hand-rolled over PMF internals down into the -library, so the provenance model and dice math stay owned here. All additive -except the Elemental-Adept bounce fix noted below. +library, so the provenance model and dice math stay owned here, and adds a +**composable scale node** so a scaled/rounded sub-roll can nest inside a larger +damage payload (per-damage-type resistance / immunity / vulnerability). All +additive except the Elemental-Adept bounce fix noted below. ### Added +- **`RollBuilder.scaleResult(numerator, denominator = 1, rounding = 'floor')`** — + wraps a builder in a composable `scale` AST node that scales its resolved PMF + by `numerator / denominator` with the given rounding. Unlike the old + `.half()` wrapper, a scaled builder composes: it survives `sumRolls(...)` + instead of being dropped on a flat-config merge, so a per-type resisted or + doubled sub-roll keeps its own scaling inside a larger hit/crit payload. The + rendered expression reflects it — `denominator === 1 → "N * (child)"`, + `numerator === 1 → "(child) // D"`, general → `"(child) * N // D"`. `.half()` + is now `scaleResult(1, 2, 'floor')`. +- **`sumRolls(parts: RollBuilder[])`** — additive factory whose `toAST()` is an + `add` node over each part's AST, letting scaled and plain children sit side by + side without the flat `.plus()` merge collapsing them. `toExpression()` joins + the parts with ` + ` and `toPMF()` convolves them. - **`PMF.applyHitFrequency(frequency)`** — provenance-preserving mass redistribution for effects that only occur with some probability (conditional attacks, on-hit riders, sub-one AoE fractions): scales every hit bin (damage diff --git a/package.json b/package.json index 853841b..cbfafef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@yipe/dice", - "version": "0.4.0", + "version": "0.5.0", "description": "A high-performance dice probability engine for D&D 5e DPR calculations. Powers dprcalc.com.", "keywords": [ "dnd",